<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Serhii Vasylenko</title>
    <description>The latest articles on DEV Community by Serhii Vasylenko (@svasylenko).</description>
    <link>https://dev.to/svasylenko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg</url>
      <title>DEV Community: Serhii Vasylenko</title>
      <link>https://dev.to/svasylenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/svasylenko"/>
    <language>en</language>
    <item>
      <title>Layers Made It Universal. Harnesses Made It Run</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Fri, 24 Apr 2026 21:15:56 +0000</pubDate>
      <link>https://dev.to/svasylenko/layers-made-it-universal-harnesses-made-it-run-2307</link>
      <guid>https://dev.to/svasylenko/layers-made-it-universal-harnesses-made-it-run-2307</guid>
      <description>&lt;p&gt;&lt;em&gt;A continuation of &lt;a href="https://devdosvid.blog/2026/03/05/flip-the-axis-a-layer-based-approach-to-multi-service-migrations/" rel="noopener noreferrer"&gt;Flip the Axis: A Layer-Based Approach to Multi-Service Migrations&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You can't script your way across a fleet of snowflake repositories. Neither can you just ask an AI agent to "migrate this service" and hope. What worked for the eight-quarter migration was a harness -- a prompt pipeline in which each layer was a sequence of ordered steps: some calling scripts for deterministic changes, some using AI to discover and adapt, and some validating the results. The harness chained their outputs, ran each layer across 21 repos at once, and landed merge requests when it was done.&lt;/p&gt;

&lt;p&gt;Here's what that looked like in practice -- including the parts we got wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  From methodology to machinery
&lt;/h2&gt;

&lt;p&gt;The previous article ended on a line worth repeating: &lt;em&gt;the methodology enables the tooling, not the other way around&lt;/em&gt;. This post is where that line becomes machinery.&lt;/p&gt;

&lt;p&gt;The project: migrating 21 services from ECS to EKS -- the final wave of an eight-quarter effort. Four engineers, targeting roughly ten repos per engineer per day on a given layer. The services were snowflakes: each with its own code style, CI configuration, logging framework, naming conventions, and infrastructure setup. The layers defined &lt;em&gt;what&lt;/em&gt; to do -- add an OIDC provider, swap the logging appender, rewrite the CI pipeline, set up a piece of infra -- and the action was identical across services. But &lt;em&gt;how&lt;/em&gt; to execute each layer varied across repositories. Same change, different wiring, 21 times over -- toil that doesn't yield to a single script. The layer approach made that pace imaginable. The harness made it real -- though the most important piece wasn't what we built first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a layer runs
&lt;/h2&gt;

&lt;p&gt;Every layer ran through the same pipeline -- a workflow of ordered prompts that mixed step types.&lt;/p&gt;

&lt;p&gt;Take logging. One service uses &lt;code&gt;logback.xml&lt;/code&gt; and is Java, another uses a custom logging setup and is NodeJS, and another has a custom appender in a shared library. You can't script that discovery -- but you &lt;em&gt;can&lt;/em&gt; script adding the dependency once the agent finds the right config. So the workflow does both: an AI step to discover and adapt, a script step for the deterministic edit, and a validation step to check the result.&lt;/p&gt;

&lt;p&gt;Some steps called Go tools for deterministic changes -- known target, computable edit, no reasoning required. Others used AI to discover and adapt: Terraform is a good example -- we could not realistically script the changes, but we could give the LLM the modules to use for adding new configuration. Others validated what the earlier ones produced. The pipeline chained their outputs, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What a script produced in step 3 informed what the agent analyzed in step 4.&lt;/li&gt;
&lt;li&gt;What the agent implemented in step 5 was what the validation prompt checked in step 7.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Why "ask Claude to migrate this service" fails
&lt;/h2&gt;

&lt;p&gt;You cannot ask an AI agent to "migrate this service to EKS." The task is too broad. The context is too large. The agent will hallucinate a plausible-looking solution, skip steps it decided weren't important, or produce something that &lt;em&gt;looks&lt;/em&gt; right and isn't.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
The failure mode isn't that the AI is dumb. It's that the task has no structure, so the agent invents its own -- and its structure drifts between runs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You get 21 repositories migrated in 21 different ways, with 21 different sets of mistakes to audit. That's worse than having done nothing.&lt;/p&gt;

&lt;p&gt;The fix isn't a better prompt. The fix is everything &lt;em&gt;around&lt;/em&gt; the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harness engineering, named a little late
&lt;/h2&gt;

&lt;p&gt;A few months ago, the term &lt;em&gt;harness engineering&lt;/em&gt; started showing up -- a zero-volume search term until early 2026. The idea: design the constraints and scaffolding around an LLM that make it reliable. Not whether to use AI, but to answer: what do you do &lt;em&gt;after&lt;/em&gt; it's part of your toolchain?&lt;/p&gt;

&lt;p&gt;So far, the conversation is mostly about coding agents. We got here through infrastructure migration -- and structured infra work is where the pattern fits most naturally. The changes follow patterns. The validation criteria are concrete. And the same change repeats across dozens of repos -- which is exactly what a harness is built for.&lt;/p&gt;

&lt;p&gt;The answer turns out to be unsexy: a pipeline that runs prompts in a fixed order, enforces what the agent can touch at each step, and validates its own work before declaring done.&lt;/p&gt;

&lt;p&gt;I didn't have that term in October 2025 when we built our own version of one. It's easier to name a thing after you've already built it wrong twice. We just kept running into the same failure -- the agent doing too much, too fast, with not enough guardrails -- and kept adding constraints until it stopped failing. Mutation boundaries. Ordered steps. Explicit reasoning gates. Validation passes.&lt;/p&gt;

&lt;p&gt;What was new -- for us -- was treating the harness as the &lt;em&gt;primary engineering artifact&lt;/em&gt;. Not the prompt. Not the model. The pipeline around them.&lt;/p&gt;

&lt;p&gt;The shift worth taking from it is this -- stop optimizing the prompt, start engineering the pipeline that runs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What our harness looked like
&lt;/h2&gt;

&lt;p&gt;A workflow is a sequence of numbered markdown files -- each one a step prompt. The agent runs them in order inside an isolated Claude Code session. Context compounds: what step 2 discovered informs what step 5 decides.&lt;/p&gt;

&lt;p&gt;The shape is always the same: context → discovery → analysis → planning → implementation → validation → ship → report.&lt;/p&gt;

&lt;p&gt;Not every step needs the agent to reason. Some prompts call a Go tool or script for a deterministic change -- same edit, known target, no drift. Others need AI to discover, analyze, and adapt. The workflow mixes both and outputs the chain forward.&lt;/p&gt;

&lt;p&gt;Six things make this structure trustworthy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mutation boundaries.&lt;/strong&gt; Every step is tagged &lt;code&gt;READ ONLY&lt;/code&gt;, &lt;code&gt;MAKES CHANGES&lt;/code&gt;, or &lt;code&gt;PLANNING ONLY&lt;/code&gt;. The agent knows what it's allowed to do at each point. No surprise writes during discovery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context anchoring.&lt;/strong&gt; Step 0 explains &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;. "Here is why we move this piece of infrastructure and need it to be X and Z in a new destination." "Here is why templates can't be applied blindly." The agent gets the intent before it touches any code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Domain knowledge in the prompts.&lt;/strong&gt; Hard-won lessons encoded directly: &lt;em&gt;"BOM manages versions -- you still must declare STS explicitly."&lt;/em&gt; &lt;em&gt;"Check actual pipeline behavior, not just config files."&lt;/em&gt; These are the footnotes a senior engineer would leave for a junior one. The agent gets them every time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first three give the agent the right starting position. The rest keep it from wandering.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early exits.&lt;/strong&gt; If the dependency already exists, skip to the report. If a values file for a Helm chart is already configured, skip to the report. Not every repo needs every step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicit reasoning gates.&lt;/strong&gt; Complex steps require reasoning, so the agent has to reason through the problem before acting -- no freestyle implementation -- and this must be declared explicitly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The implement/validate split.&lt;/strong&gt; For critical layers, two separate workflows ran in sequence: one to implement, one to validate. The validation workflow reviewed the implementation against defined criteria rather than rubber-stamping its own work. This did more for reliability than any other constraint we added.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is about making the agent smarter. They're about making it &lt;em&gt;predictable&lt;/em&gt;. Every constraint trades a degree of freedom for a degree of reliability. That's the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The instrument
&lt;/h2&gt;

&lt;p&gt;Workflows describe &lt;em&gt;what&lt;/em&gt; the agent should do per repo. We still needed an orchestrator to run them -- and to run them across a batch of repos at once.&lt;/p&gt;

&lt;p&gt;So we built a parallel execution wrapper on top of the Claude Agent SDK. Think of it as a prompt pipeline runner. Point it at a workflow and a list of repositories, and for each repo it clones into its own git worktree, spins up an isolated Claude Code session, runs the workflow's step files in order, enforces the mutation boundaries from the previous section, and lands a per-repo report when it's done. One engineer monitors the batch and reviews the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag3ederuhl0fqt5zojsi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag3ederuhl0fqt5zojsi.png" alt="Parallel execution diagram showing a workflow and repo list fanning out to isolated sessions per repository, converging to engineer review" width="800" height="1176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each session adapts to its repository while following the same workflow. The agent in repo A figures out one logging setup; the agent in repo B figures out a completely different one. Both produce the same kind of report. The engineer opens ten reports instead of writing ten PRs.&lt;/p&gt;

&lt;p&gt;This is the automated &lt;a href="https://devdosvid.blog/2026/03/05/flip-the-axis-a-layer-based-approach-to-multi-service-migrations/" rel="noopener noreferrer"&gt;flip-the-axis&lt;/a&gt; model. One layer, one workflow, N repos, one human in the loop. All twelve migration layers ran through this pipeline.&lt;/p&gt;

&lt;p&gt;Prompt pipeline + parallel sessions + per-repo reports + human review at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts as production code
&lt;/h2&gt;

&lt;p&gt;The non-obvious part: all of this lived in a shared repository. Workflows, runbooks, Go tools, scripts, per-service metadata -- all version-controlled in one place. The project headquarters.&lt;/p&gt;

&lt;p&gt;This wasn't convenient. It was a knowledge-sharing mechanism.&lt;/p&gt;

&lt;p&gt;When an engineer discovered a prompt needed more specificity -- say, the IAM workflow missed an edge case with cross-account trust policies -- they updated the prompt and committed it. On the next pull, every teammate got the improvement. Same for validation scripts, runbook instructions, and service metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; treat AI prompts and workflows like production code. Review changes. Iterate as a team. The prompts you write in week 1 will not be the prompts you need in week 6 -- and every improvement should propagate to everyone automatically.&lt;/p&gt;

&lt;p&gt;If your team is adopting AI for infra work and the prompts live in private gists, you've already lost the compounding advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest assessment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cross-referencing was the hardest problem we didn't fully solve.&lt;/strong&gt; The Helm values layer required correlating data from Terraform configs, ECS task definitions, and application codebases into a single file. AI agents working within a single repository can't hold that full picture. Human judgment and manual cross-checking stayed essential here, and I don't think that's going to change for this class of problem anytime soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The reliability gradient was predictable.&lt;/strong&gt; Within each workflow, the deterministic steps -- the ones calling scripts -- never drifted. The AI steps were reliable when isolated to a single file following a repeatable pattern. Reliability dropped when a step required cross-repository context or judgment calls about tradeoffs. Those stayed with humans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We validated -- but not enough.&lt;/strong&gt; The implement/validate split worked wherever we applied it -- it was the highest-leverage pattern in the whole setup. But we didn't apply it to every layer, and we should have. The same workflows we used for implementation could have run in verification mode at near-zero additional cost. Our QA and PREPROD environments caught what universal validation would have. They shouldn't have had to.&lt;/p&gt;

&lt;p&gt;If I were starting this project again tomorrow, the first thing I'd build is a validation workflow for every implementation workflow, from day one. Not as an afterthought. As a matching pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means
&lt;/h2&gt;

&lt;p&gt;Layers made the work universal. Harnesses made the AI trustworthy enough to run it -- even across 21 repos, each wired differently. Together they closed out an eight-quarter migration -- four engineers, not forty.&lt;/p&gt;

&lt;p&gt;The next time someone frames it as "just write a script" versus "just use AI" -- it's the wrong question. Build the harness that runs both.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/harness-design-long-running-apps" rel="noopener noreferrer"&gt;Harness Design for Long-Running Application Development&lt;/a&gt; -- Anthropic's engineering team on the same concept applied to agentic coding.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openai.com/index/harness-engineering/" rel="noopener noreferrer"&gt;Harness Engineering&lt;/a&gt; -- OpenAI's take on the same discipline.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.humanlayer.dev/blog/skill-issue-harness-engineering-for-coding-agents" rel="noopener noreferrer"&gt;It's a Skill Issue: Harness Engineering for Coding Agents&lt;/a&gt; -- HumanLayer on harness configuration for coding agents.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>kubernetes</category>
      <category>automation</category>
    </item>
    <item>
      <title>Flip the Axis: A Layer-Based Approach to Multi-Service Migrations</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Fri, 24 Apr 2026 21:15:41 +0000</pubDate>
      <link>https://dev.to/svasylenko/flip-the-axis-a-layer-based-approach-to-multi-service-migrations-3a5d</link>
      <guid>https://dev.to/svasylenko/flip-the-axis-a-layer-based-approach-to-multi-service-migrations-3a5d</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;When you're migrating many services through the same steps, parallelize by step, not by service. Sweep one type of change across all services, then the next – it compounds learning, catches inconsistencies early, and makes automation viable. But recognize which services don't fit the pattern: architecturally unique services should still be migrated serially.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You've probably seen the shape of this problem before. You're planning next quarter's migration – could be Kubernetes, a new database engine, a cloud provider switch, a major framework version bump. You count the services. You count the engineers. The math doesn't work.&lt;/p&gt;

&lt;p&gt;Here's what it looked like for us: 2025 Q4 planning, 21 services still running on ECS (Amazon's container orchestration service) that needed to move to EKS (their managed Kubernetes platform). A headcount cut left us with 4 engineers. Each service migration had been taking 3-4 weeks of effort. The project had already been running since January 2024 – nearly two years – and the serial execution model from the previous quarter had required 8 engineers for 19 services. We had half the people, more services, and a timeline that was starting to feel permanent.&lt;/p&gt;

&lt;p&gt;Nobody was telling us it had to be done next quarter. Our management said, "It's okay if we can't, don't worry."&lt;/p&gt;

&lt;p&gt;But you know what happens to migrations that stretch for many months. They lose momentum. Engineers rotate off. Institutional knowledge erodes. The remaining services – always the hardest ones – sit in a permanent "next quarter" backlog.&lt;/p&gt;

&lt;p&gt;You don't have a staffing problem. You have an execution model problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why serial breaks down
&lt;/h3&gt;

&lt;p&gt;The default migration approach is serial: one engineer owns a service end-to-end and walks it through every step – networking, permissions, environment adjustments, certificates, CI/CD, DNS, cleanup. This works fine for a couple of services. This breaks down at scale.&lt;/p&gt;

&lt;p&gt;The engineer context-switches across completely different types of work – networking, then application configuration, then debugging a permissions issue – and never builds deep fluency in any of them. Services are unique snowflakes – each with its own code style, dependency patterns, and configuration quirks. Serial migration means absorbing that uniqueness for every service, at every step.&lt;/p&gt;

&lt;p&gt;Even worse – learning stays siloed. An engineer who figured out a networking edge case in week 2 can't help the engineer who hits the same issue in week 6 – by then, they've moved on. Everyone is deep in a different service, at a different stage. The team can't effectively pair, review, or unblock each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight
&lt;/h2&gt;

&lt;p&gt;When I looked at what we'd actually done in Q3 – service by service, step by step – the pattern was obvious in hindsight: we were doing the same work over and over again. Networking, permissions, application setup – identical across services.&lt;/p&gt;

&lt;p&gt;It only looked unique because we were thinking one service at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60ebt6wiabt8mqix5eop.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60ebt6wiabt8mqix5eop.webp" alt="Serial Migration Strategy" width="800" height="756"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if we flipped the axis? Instead of completing all steps for one service before moving to the next, complete one step across all services before moving to the next step.&lt;/p&gt;

&lt;p&gt;That's the core of the layer-based approach. A &lt;strong&gt;layer&lt;/strong&gt; is one type of infrastructure or configuration change, applied to every service in the migration scope. You sweep through all services at one layer, validate, then move to the next layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmatv30vyo7q8wvay4uro.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmatv30vyo7q8wvay4uro.webp" alt="Layer-based Migration Strategy" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repetition builds expertise.&lt;/strong&gt; By the third service in a layer, you've seen the pattern. By the tenth, you're fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-service checks catch errors early.&lt;/strong&gt; When you're applying the same change to 20 services in a row, inconsistencies become obvious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning compounds across the team.&lt;/strong&gt; Everyone works the same layer simultaneously – discoveries spread instantly instead of weeks later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation becomes viable.&lt;/strong&gt; Identical changes across services are exactly what tooling excels at – predictable patterns with minor per-service variations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Defining Your Layers
&lt;/h2&gt;

&lt;p&gt;The number of layers depends on your migration. Ours had 14. Yours might have 8 or 20.&lt;/p&gt;

&lt;p&gt;Here are the categories we found useful, grouped by concern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery:&lt;/strong&gt; mapping downstream dependencies – services, databases, endpoints, protocols&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connectivity:&lt;/strong&gt; networking between environments, firewall configurations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity:&lt;/strong&gt; permissions, service accounts, trust policies, OIDC configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App level security:&lt;/strong&gt; certificates, TLS termination, WAF rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application:&lt;/strong&gt; runtime configuration, environment variables, secrets, logging adjustments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery:&lt;/strong&gt; CI/CD pipelines, ingress and routing, traffic management for gradual rollout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your categories will differ. The names don't matter – the decomposition does.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to decompose your own migration
&lt;/h3&gt;

&lt;p&gt;Start from a single service migration you've already done. List every change you made, in order. Group changes by type, not by when they happened. Each group is a candidate layer.&lt;/p&gt;

&lt;p&gt;Then validate: can this layer be applied independently of the next one? Can you validate it before moving on? If yes, it's a good layer boundary. If two changes are tightly coupled and can't be validated separately, merge them into one layer.&lt;/p&gt;

&lt;p&gt;One rule we learned the hard way: &lt;strong&gt;one layer per pull request.&lt;/strong&gt; Early on, some PRs combined changes from multiple layers – networking and permissions in the same commit. Validation got complex, rollbacks got messy. Keep them separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution Model
&lt;/h2&gt;

&lt;p&gt;A layer sweep works like this: the team takes on a layer, splits the service list among themselves, and each engineer applies that layer to their assigned services. Everyone works the same type of change simultaneously.&lt;/p&gt;

&lt;p&gt;One engineer can realistically sweep a single layer across 6-8 services in a day. That number surprises people – until they know the tooling. We paired the layered methodology with AI-assisted automation that handled the repetitive configuration work across services. But the important thing is: &lt;strong&gt;the layer-based structure is what makes that automation possible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When every service needs the same type of change with minor variations, you can build prompts, scripts, and validation checks that apply across the board. Serial, per-service work is too varied to automate effectively. The AI tooling story – what worked, what failed, and where human judgment was irreplaceable – is the subject of the next post in this series.&lt;/p&gt;

&lt;p&gt;During execution, the team meets briefly to sync on edge cases – because the work is homogeneous, an edge case in one service is immediately relevant to every other service going through the same layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress tracking
&lt;/h3&gt;

&lt;p&gt;A simple table – one column per layer, one row per service – serves as the source of truth. The team updates it in real time. Status per cell: not started, in progress, done, blocked, not applicable. This sounds basic, but it's surprisingly effective. You can see at a glance where the project stands, which layers are complete, and where blockers are clustering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services that don't fit
&lt;/h3&gt;

&lt;p&gt;Not every service fits the pattern. In our case, 4 out of 21 services were architecturally complex enough that the layered approach didn't help – they required deep, per-service analysis that negated the speed advantage.&lt;/p&gt;

&lt;p&gt;We recognized this early and migrated them serially, with dedicated engineers working in parallel with the layer sweeps. Trying to force these into the pattern would have slowed everything down.&lt;/p&gt;

&lt;p&gt;The lesson: the layer-based approach is a force multiplier for homogeneous work. When a service is genuinely unique, serial migration is the right tool. Budget for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordination That Matches the Work
&lt;/h2&gt;

&lt;p&gt;The coordination model that works during one phase can hurt you in the next.&lt;/p&gt;

&lt;h3&gt;
  
  
  During layers: synchronize
&lt;/h3&gt;

&lt;p&gt;When the whole team works the same layer, synchronous coordination is natural and cheap. Team syncs are short because everyone has context on the same type of work. An edge case discovered by one engineer is immediately useful to the others. Knowledge transfer happens without any deliberate mechanism – the work itself is identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  During traffic switching: structure async handoffs
&lt;/h3&gt;

&lt;p&gt;When the project moves from layer execution to per-service traffic switching, the work diverges. Each service has its own timeline, its own blockers, its own owning team with a different schedule. Synchronous coordination becomes expensive – the team is now working on different problems.&lt;/p&gt;

&lt;p&gt;This is where a &lt;strong&gt;handoff log&lt;/strong&gt; pays for itself. A shared document – not "made progress on Service X" but the actual PR link, the specific blocker, the decision to skip WAF configuration for this service, and why. What made it work: specificity over summary, explicit ownership, and early surfacing of blockers.&lt;/p&gt;

&lt;p&gt;We heavily used this approach during the last phase of migration – the traffic switch – when two team members went to our SF hub to be on site with service owners, and two stayed in Berlin. But this isn't a timezone trick – it works for co-located teams just as well. Fewer meetings, more focused execution, and a written record that prevents "I thought you were handling that" conversations.&lt;/p&gt;

&lt;p&gt;The lesson: match the coordination model to the shape of the work. When work is homogeneous, synchronize. When it diverges, structure async handoffs and get out of each other's way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Traffic Switch Cadence
&lt;/h2&gt;

&lt;p&gt;Layer execution is predictable. Traffic switching is where the surprises live.&lt;/p&gt;

&lt;p&gt;We used a graduated weekly cadence: Monday preflight (verify hostnames, certificates, ingress, autoscaling, dashboards – deploy one instance), Tuesday scale up and shift 1% of traffic, Wednesday observe and fix, Thursday shift to 50%, Friday observe and fix, following Monday shift to 100%.&lt;/p&gt;

&lt;p&gt;The observation days weren't idle – they were when most debug work happens. Issues that don't surface at 1% show up at 50%. Fixes discovered for one service often apply to others in the same batch.&lt;/p&gt;

&lt;p&gt;Batch your traffic switches. Running multiple services through this cadence simultaneously amortizes the coordination overhead – the preflight checklist, once built, applies to every service.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Works (and When It Doesn't)
&lt;/h2&gt;

&lt;p&gt;The layered approach is not universal. It works well under specific conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use layers when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The migration is decomposable into independent, repeatable steps&lt;/li&gt;
&lt;li&gt;The same type of change applies across many targets with minor per-service variations&lt;/li&gt;
&lt;li&gt;Changes can be batch-validated – all services at one layer before moving on&lt;/li&gt;
&lt;li&gt;The team is small relative to the workload and needs a &lt;strong&gt;force multiplier&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use serial when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services are architecturally unique and complex, and require deep, per-service analysis&lt;/li&gt;
&lt;li&gt;The number of targets is small enough that coordination overhead outweighs the parallelization benefit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not an either/or decision. In our migration, the layered approach covered 17 of 21 services. The remaining 4 were migrated serially. Recognizing which services don't fit the pattern early is just as important as the pattern itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start the handoff log from day one.&lt;/strong&gt; We introduced it when the team split across workstreams during traffic switching. In retrospect, the discipline of specificity and explicit ownership helps even when everyone is in the same room working the same layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run validation sweeps after each layer, not at the end.&lt;/strong&gt; We deferred some validation to later phases, when we did traffic switch on preproduction environments, which made fixing errors more expensive and created pressure during the most time-sensitive window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define service owner readiness criteria upfront.&lt;/strong&gt; Some services reached the traffic switch phase with owners who weren't fully briefed, dashboards that weren't adjusted, etc. Clear criteria before the switch phase would have eliminated friction during the highest-pressure window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan for the energy arc.&lt;/strong&gt; An intensive, multi-month migration grinds people down. Build rotation points into the plan. Bring fresh perspective at deliberate moments – especially before the production switch phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track decisions explicitly, separate from action items.&lt;/strong&gt; Some decisions logged in the handoff document were missed because they were buried among task updates. A dedicated "decisions" section prevents teams from diverging without realizing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Flip the axis.&lt;/strong&gt; When many services go through the same steps, parallelize by step, not by service. The efficiency gain comes from repetition, shared learning, and automation – not from working harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define your layers by decomposing a single service migration.&lt;/strong&gt; Group changes by type, validate that layers can be applied independently, and enforce one layer per merge request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match coordination to the shape of the work.&lt;/strong&gt; Synchronize when work is homogeneous. Structure async handoffs when it diverges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recognize what doesn't fit the pattern.&lt;/strong&gt; Some services are genuinely unique. Budget for serial migration alongside the layer sweeps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The traffic switch is its own phase.&lt;/strong&gt; Layer execution is predictable. Traffic switching is where surprises live. Treat it with a graduated cadence and observation days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The methodology enables the tooling, not the other way around.&lt;/strong&gt; We paired layer-based execution with AI-assisted automation – and that's what made one engineer sweeping 6-8 services in a day realistic. But the automation only worked &lt;em&gt;because&lt;/em&gt; the layers created predictable, repeatable patterns. That story is next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If this feels like a problem you've hit – or you're about to – I'd like to hear your approach. Same constraint, different solution? A migration where layers didn't work? Drop a comment.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>architecture</category>
      <category>sre</category>
    </item>
    <item>
      <title>Full Attendance, Zero Presence</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Thu, 23 Apr 2026 21:33:08 +0000</pubDate>
      <link>https://dev.to/svasylenko/full-attendance-zero-presence-fhp</link>
      <guid>https://dev.to/svasylenko/full-attendance-zero-presence-fhp</guid>
      <description>&lt;p&gt;Five minutes after the all-hands, someone asks me in the hallway, "What did he mean by that?" Then another person: "So what are we actually supposed to do?" I didn't have a great answer either. We'd all sat through twenty minutes of a strategy change presentation. We all nodded. None of us was in the conversation.&lt;/p&gt;

&lt;p&gt;This isn't a one-off. I see this pattern everywhere – across teams, across functions, across seniority levels. People show up to meetings. They sit. They nod. They leave without having processed what was said. Full attendance, zero presence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most advice on listening tells you to add behaviors – nod more, paraphrase, ask clarifying questions. I think the fix is the opposite. Presence isn't something you perform. It's what remains when you stop doing everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't a listening guide. What follows are patterns – ways presence breaks down in rooms I've been in, including when I was the one not present. Once you start recognizing them, they're hard to unsee.&lt;/p&gt;

&lt;h2&gt;
  
  
  How presence breaks down
&lt;/h2&gt;

&lt;p&gt;I see this play out in a few distinct ways. Sometimes it's obvious – a room full of silent nods. Sometimes it's subtler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent nods, two flavors
&lt;/h3&gt;

&lt;p&gt;This is the one that does the most damage, and it's invisible in the moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flavor one: don't understand, won't say so.&lt;/strong&gt; That all-hands I described at the top – this was exactly it. A room full of people nodding along to something they hadn't actually processed. Nobody asked a clarifying question. Nobody said, "I don't follow." The meeting felt smooth. The hallway afterward told the real story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flavor two: understand fine, disagree, won't surface it.&lt;/strong&gt; A manager assigned a direction. The team silently accepted. Later, over coffee, one of the engineers told me directly: he disagreed, saw no value in it, and was going to do the minimum to get it off his plate.&lt;/p&gt;

&lt;p&gt;The disagreement never entered the room where it could have changed something. It lived in the hallway, in the coffee chat, in the Slack DM. By the time it surfaced – slow execution, low quality, quiet attrition – nobody connected it back to that meeting where everyone nodded.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Both flavors look identical in the room. Silent nods. But one is a comprehension gap, and the other is a trust gap. A speaker who's actually paying attention to the room – not just broadcasting – can sometimes tell the difference. Yet most speakers don't, because they're focused on getting through their material. They're not listening either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Solving before hearing
&lt;/h3&gt;

&lt;p&gt;Engineers are trained to decompose problems. It's the job. But that instinct kicks in too early in conversations. Someone starts describing a situation. Ten seconds in, your brain is already pattern-matching and mapping it to something you've seen before, composing a solution.&lt;/p&gt;

&lt;p&gt;The problem: you're now building your response in parallel with the other person still talking. You're not tracking their reasoning – you're constructing your own. By the time they finish, you've got an answer to a question they didn't ask, because you stopped listening after the first thirty seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listening for your turn
&lt;/h3&gt;

&lt;p&gt;This one I catch myself doing. Someone is talking, and I'm holding my argument in my head, waiting for a gap. The physical tell is real – I lean forward, I nod faster. Not because I agree, but because I want them to finish so I can say my thing.&lt;/p&gt;

&lt;p&gt;People who are vocal and not shy do this without thinking. They fire their thoughts at every opening. The quieter person's point never fully lands because the room has already moved on to the next response.&lt;/p&gt;

&lt;p&gt;The worst part: you don't realize you're doing it until someone's point slides past you and you can't recall what they actually said. And when it happens to me – when I'm the one not being heard – I push through. Force the idea through anyway. It works, but it's not solving the listening problem. It's bulldozing past it.&lt;/p&gt;

&lt;h3&gt;
  
  
  It happens in 1:1s too
&lt;/h3&gt;

&lt;p&gt;You might think this is a group-meeting problem – too many people, too many laptops, too much noise. But it happens in the most intimate format we have: the 1:1.&lt;/p&gt;

&lt;p&gt;I've caught myself doing it. Someone is talking to me one-on-one, and my eyes drift to the screen. I'm scanning something – a notification, a message, a tab I forgot to close. Tracking something that isn't the person in front of me. Not because I don't care. Because the pull of the inbox is stronger than any of us wants to admit.&lt;/p&gt;

&lt;p&gt;This is the version of the problem that's hardest to name, because it feels personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What being present actually looks like
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Closing the lid
&lt;/h3&gt;

&lt;p&gt;I was in a meeting with my team. Everyone had laptops open – the usual. Someone was presenting an idea, and the room had that familiar energy: half-listening, half-typing.&lt;/p&gt;

&lt;p&gt;I closed my laptop lid. Started looking at the speaker. Actually tracking what they were saying.&lt;/p&gt;

&lt;p&gt;I saw something shift in their eyes – a kind of recognition that someone was paying attention. Then one of my teammates closed their lid too. Then another. Not everyone, but enough. The conversation changed. Suddenly, there were real questions. Pushback. Ideas building on each other. Before the lids closed, it was a broadcast. After, it was a dialogue.&lt;/p&gt;

&lt;p&gt;That small act – closing a laptop – did more for the conversation than any "great question" could have. Because the question only works if you've been listening long enough to know what to ask.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dropping performance
&lt;/h3&gt;

&lt;p&gt;Here's the thing about active listening that most advice gets wrong: it's not a technique. If you try to perform it – the nodding, the eye contact, the clarification question every twenty seconds – people notice. They feel the gap between your body language and your attention.&lt;/p&gt;

&lt;p&gt;It only works when you actually stop thinking about everything else. You look at the person. Not staring – eye contact from time to time. You let them flesh out their thoughts. You can be completely silent. That's fine. Nobody needs you to nod on a timer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The real skill is subtraction, not addition. Remove the distractions. Remove the urge to respond. Remove the performance. What's left is just… focus. You're in this conversation and nowhere else. That's simpler than any framework, and harder than all of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Holding back
&lt;/h3&gt;

&lt;p&gt;Before a recent vacation, I was handing over a project I'd been leading – research done, core team assembled, draft plan written. My teammate needed to pick it up and run with it.&lt;/p&gt;

&lt;p&gt;In our 1:1 handover meeting, she started asking questions and sharing her own vision for how to approach it. I remember the tension in my own head – I knew the answers. I had my understanding, my vision, my plan. Every instinct said: jump in, push the knowledge, save time.&lt;/p&gt;

&lt;p&gt;I didn't say a word. I just listened. Looked at her screen when she showed me things. Nodded – naturally, not performatively.&lt;/p&gt;

&lt;p&gt;She covered the whole story. Answered the majority of her own questions just by working through the plan and my notes. And then she proposed a couple of insights I had missed in the planning – things she added as follow-up topics for the project team.&lt;/p&gt;

&lt;p&gt;I'm fairly sure that wouldn't have happened if I'd started in expert mode. The handover would have been faster, but worse. She would have received my understanding instead of building her own. And those insights she caught? Buried under my monologue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling the rope
&lt;/h3&gt;

&lt;p&gt;Here's how I think about it. When someone is explaining a hard problem – the kind that doesn't have clean edges – they're pulling a heavy rock up from the bottom on a rope.&lt;/p&gt;

&lt;p&gt;You can stand there and watch them pull. Or you can step closer, grab the rope, and help.&lt;/p&gt;

&lt;p&gt;Active listening is the grabbing. Resist the urge to decompose immediately. Follow the other person's thread before you start your own. That's the rope-pulling: making the effort to understand what they're actually saying before you decide whether you agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;The next time you're in a room, you'll probably catch one of these – in yourself or someone else. That's the point. Once you see the pattern, you can't run it on autopilot anymore. You don't need a technique for what happens next. You just stop doing the thing you caught yourself doing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Presence is not attendance. Being in the room and being in the conversation are different things.&lt;/li&gt;
&lt;li&gt;Engineers default to problem-solving mode, which means they start constructing responses before they've finished hearing the problem. Resist the decomposition reflex.&lt;/li&gt;
&lt;li&gt;Silent nods hide two different problems: people who don't understand and people who disagree. Both look the same. Both break things differently.&lt;/li&gt;
&lt;li&gt;Listening is labor, not passivity. You're helping the speaker articulate something hard, not waiting for them to finish so you can talk.&lt;/li&gt;
&lt;li&gt;Listening isn't a skill to add to your toolkit. It's setting down reflexes you already have – the solving, the responding, the performing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Steve Huynh&lt;/strong&gt; — on how the questions you ask communicate how you think: &lt;a href="https://alifeengineered.substack.com/p/5-questions-that-make-people-take" rel="noopener noreferrer"&gt;5 Questions That Make People Take You Seriously&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wes Kao&lt;/strong&gt; — on detecting and addressing the &lt;a href="https://newsletter.weskao.com/p/question-behind-the-question" rel="noopener noreferrer"&gt;Question behind the question&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>leadership</category>
      <category>career</category>
      <category>productivity</category>
      <category>management</category>
    </item>
    <item>
      <title>A Deep Dive Into Terraform Static Code Analysis Tools: Features and Comparisons</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Tue, 16 Apr 2024 23:32:03 +0000</pubDate>
      <link>https://dev.to/svasylenko/a-deep-dive-into-terraform-static-code-analysis-tools-features-and-comparisons-1kbf</link>
      <guid>https://dev.to/svasylenko/a-deep-dive-into-terraform-static-code-analysis-tools-features-and-comparisons-1kbf</guid>
      <description>&lt;p&gt;Many teams employ Terraform by HashiCorp to efficiently manage their infrastructure, leveraging its ability to automate the lifecycle of complex environments. Yet, integrating security scanning into Terraform pipelines often remains overlooked, exposing these environments to potential security risks and compliance issues.&lt;/p&gt;

&lt;p&gt;This article explores several prominent static code analyzers that support Terraform code and focus on its security scanning. This comparison will guide teams in choosing the right tool to enhance their security measures within Terraform workflows, ensuring safer and more compliant infrastructure management.&lt;/p&gt;

&lt;p&gt;Here are the tools we'll be reviewing: &lt;strong&gt;KICS&lt;/strong&gt;, &lt;strong&gt;tfsec&lt;/strong&gt;, &lt;strong&gt;Trivy&lt;/strong&gt;, &lt;strong&gt;Terrascan&lt;/strong&gt;, &lt;strong&gt;Checkov&lt;/strong&gt;, and &lt;strong&gt;Semgrep OSS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While many of these tools also support other platforms and technologies, &lt;strong&gt;this review will concentrate exclusively on their functionality with Terraform.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Why use Static Code Analysis for Terraform
&lt;/h2&gt;

&lt;p&gt;Static code analysis tools are necessary to enhance the security of Terraform-managed infrastructures. Unlike linters, these tools focus not on syntax errors or coding style but delve deeply into the code to identify security vulnerabilities and potential compliance issues without running the actual code. This proactive approach to security helps safeguard the infrastructure from potential threats before deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Early Detection: Identifies security vulnerabilities and misconfigurations early in development, preventing them from reaching production. &lt;/li&gt;
&lt;li&gt;Compliance Assurance: Ensures Terraform code complies with industry standards and internal security policies.&lt;/li&gt;
&lt;li&gt;Automated Security Integration: Seamlessly integrates with CI/CD pipelines, automating security checks to maintain a continuous focus on security.&lt;/li&gt;
&lt;li&gt;Actionable Insights: Delivers detailed vulnerability reports, facilitating swift and effective resolution.&lt;/li&gt;
&lt;li&gt;Scalability: Effectively handles increasing project complexity and size, maintaining rigorous security standards without additional manual effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Expected Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy Coverage&lt;/strong&gt;: The tool should offer comprehensive scanning capabilities to detect security vulnerabilities specific to Infrastructure as Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable Security Policies&lt;/strong&gt;: It must allow users to define and adjust security policies and severity levels to align with specific project needs or compliance requirements. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seamless Integration&lt;/strong&gt;: The analyzer should integrate effortlessly with existing CI/CD tools and version control systems, facilitating a smooth workflow. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detailed Reporting&lt;/strong&gt;: Clear and actionable reports are crucial. The tool should prioritize issues based on severity and provide practical steps for remediation. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanning Customization&lt;/strong&gt;: Users should be able to tailor the scanning process to focus on particular aspects of the codebase, enabling targeted and efficient security assessments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a clear understanding of the necessary features in a static code analyzer, which tools on the market best fulfill these criteria?&lt;/p&gt;

&lt;p&gt;Let's take a closer look at some leading options!&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Static Code Analyzers for Terraform
&lt;/h2&gt;

&lt;p&gt;Following on what makes a static code analyzer robust, let's dive into some open-source tools that exemplify these essential features.&lt;/p&gt;

&lt;p&gt;I picked six tools for my review. I know there are more on the market, but I focused on &lt;strong&gt;open-source, free-to-use&lt;/strong&gt; tools and those that provide at least &amp;gt;100 out-of-the-box scanning policies for Terraform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KICS&lt;/strong&gt; (stands for "Keeping Infrastructure as Code Secure"): &lt;br&gt;
Owner/Maintainer: Checkmarx&lt;br&gt;
Age: First released on GitHub on November 30th, 2020&lt;br&gt;
License: &lt;a href="https://github.com/Checkmarx/kics/blob/master/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;br&gt;
Owner/Maintainer: Aqua Security (acquired in 2021)&lt;br&gt;
Age: First released on GitHub on March 5th, 2019&lt;br&gt;
License: &lt;a href="https://github.com/aquasecurity/tfsec/blob/master/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;br&gt;
&lt;em&gt;tfsec project is no longer actively maintained in favor of the Trivy tool. But because many people still use it and it's quite famous, I added tfsec to this comparison.&lt;br&gt;
However, I recommend against using it for new projects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;br&gt;
Owner/Maintainer: Aqua Security&lt;br&gt;
Age: First released on GitHub on May 7th, 2019&lt;br&gt;
License: &lt;a href="https://github.com/aquasecurity/trivy/blob/main/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;br&gt;
&lt;em&gt;backward-compatible with tfsec&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;br&gt;
Owner/Maintainer: Tenable (acquired in 2022)&lt;br&gt;
Age: First release on GitHub on November 28th, 2017&lt;br&gt;
License: &lt;a href="https://github.com/tenable/terrascan/blob/master/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;br&gt;
Owner/Maintainer: Prisma Cloud by Palo Alto Networks (acquired in 2021)&lt;br&gt;
Age: First released on GitHub on March 31st, 2021&lt;br&gt;
License: &lt;a href="https://github.com/bridgecrewio/checkov/blob/main/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;br&gt;
Owner/Maintainer: Semgrep&lt;br&gt;
Age: First release on GitHub on February 6th, 2020&lt;br&gt;
License: &lt;a href="https://github.com/semgrep/semgrep/blob/develop/LICENSE" rel="noopener noreferrer"&gt;GNU Lesser General Public License v2.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These tools are essential in enhancing Terraform's security posture and reflect a strong collaboration between open-source communities and enterprise backing. This blend ensures that the tools are not only accessible but also robustly maintained and up-to-date.&lt;/p&gt;

&lt;p&gt;Let’s explore how these tools stack up regarding features and usability.&lt;/p&gt;
&lt;h2&gt;
  
  
  Comparing Out-of-the-Box Policies and Terraform Providers
&lt;/h2&gt;

&lt;p&gt;Understanding the number and variety of default policies each tool offers is crucial for those just beginning to explore security automation for Terraform.&lt;/p&gt;

&lt;p&gt;The extent of out-of-the-box policies can significantly ease the integration process of static analysis by providing immediate and comprehensive insights into potential security and compliance issues. Similarly, the number of supported Terraform Providers also plays a critical role.&lt;/p&gt;

&lt;p&gt;In this chapter, we delve into these foundational features across observed tools, helping you pinpoint which one could best satisfy your requirements for robust, ready-to-use security scanning.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Policies&lt;/th&gt;
&lt;th&gt;Supported Terraform Providers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KICKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;663&lt;/td&gt;
&lt;td&gt;aws, azure, gcp, kubernetes, alicloud, databricks, github, nifcloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;td&gt;aws, azure, gcp, digitalocean, kubernetes, cloudstack, github, openstack, oracle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;322&lt;/td&gt;
&lt;td&gt;aws, azure, gcp, digitalocean, cloudstack, github, oracle, openstack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;790&lt;/td&gt;
&lt;td&gt;aws, azure, gcp, digitalocean, kubernetes, docker, github&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2110&lt;/td&gt;
&lt;td&gt;aws, azure, gcp, digitalocean, kubernetes, github, gitlab, ibm, linode, openstack, alicloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;362&lt;/td&gt;
&lt;td&gt;aws, azure, gcp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, all tools support the "Big Three" cloud service Terraform providers—AWS, Azure, and GCP—for managing resources on these popular platforms.&lt;/p&gt;

&lt;p&gt;With over 2000 out-of-the-box policies, Checkov significantly stands out from the competition. This tool also leads in the total number of supported Terraform providers.&lt;/p&gt;

&lt;p&gt;While the default policies provide a strong foundation for security scanning, the ability to tailor these policies is just as crucial. Next, we'll explore how each tool accommodates custom policy capabilities, allowing you to fine-tune the policies to fit your project's specific requirements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Custom Policy Capabilities
&lt;/h2&gt;

&lt;p&gt;Default policies serve as the foundation, but the nuances of each project demand the extension of this base.&lt;/p&gt;

&lt;p&gt;Here, we delve into how each tool enables you to add custom policies, thus enhancing and refining the provided defaults.&lt;/p&gt;

&lt;p&gt;While all six tools support adding custom policies to their default set, they differ in terminology: 'policy' is the common term, whereas KICS refers to them as 'queries,' and Semgrep calls them 'rules.'&lt;/p&gt;

&lt;p&gt;Regarding policy syntax:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OPA Rego&lt;/strong&gt; syntax is used by &lt;a href="https://docs.kics.io/latest/creating-queries/" rel="noopener noreferrer"&gt;KICS&lt;/a&gt;, &lt;a href="https://aquasecurity.github.io/trivy/v0.50/docs/scanner/misconfiguration/custom/" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;, &lt;a href="https://aquasecurity.github.io/tfsec/latest/guides/rego/rego/" rel="noopener noreferrer"&gt;tfsec&lt;/a&gt;, and &lt;a href="https://runterrascan.io/docs/policies/policies/" rel="noopener noreferrer"&gt;Terrascan&lt;/a&gt;.  It's a powerful language widely adopted in the industry, though there's a learning curve that could pay dividends for future projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YAML&lt;/strong&gt; syntax is used by &lt;a href="https://www.checkov.io/3.Custom%20Policies/Custom%20Policies%20Overview.html" rel="noopener noreferrer"&gt;Checkov&lt;/a&gt; and &lt;a href="https://semgrep.dev/docs/writing-rules/rule-syntax/" rel="noopener noreferrer"&gt;Semgrep&lt;/a&gt;. This offers a familiar and straightforward start, with Checkov also allowing policies to be written in Python, albeit with some constraints. With YAML, the ease of use is balanced against the limitations set by the tool's capabilities.&lt;/p&gt;

&lt;p&gt;Understanding these differences will guide you to a tool that matches your security requirements, your team's expertise, and the scope of your infrastructure projects.&lt;/p&gt;

&lt;p&gt;To illustrate, here is an example of a KICS Rego policy checking for default RDS instance ports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package Cx

import data.generic.common as common_lib
import data.generic.terraform as tf_lib

CxPolicy[result] {
    db := input.document[i].resource.aws_db_instance[name]

    enginePort := common_lib.engines[e]

    db.engine == e
    db.port == enginePort

    result := {
        "documentId": input.document[i].id,
        "resourceType": "aws_db_instance",
        "resourceName": tf_lib.get_resource_name(db, name),
        "searchKey": sprintf("aws_db_instance[%s].port", [name]),
        "issueType": "IncorrectValue",
        "keyExpectedValue": sprintf("aws_db_instance[%s].port should not be set to %d", [name, enginePort]),
        "keyActualValue": sprintf("aws_db_instance[%s].port is set to %d", [name, enginePort]),
        "searchLine": common_lib.build_search_line(["resource", "aws_db_instance", name, "port"], []),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an example of a Checkov YAML policy forbidding specific EC2 instance types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Org's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;compute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;instances&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;should&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;p5.48xlarge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;p4d.24xlarge"&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME_AWS_FORBIDDEN_EC2_TYPES"&lt;/span&gt;
 &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NETWORKING"&lt;/span&gt;
&lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;or&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cond_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attribute"&lt;/span&gt;
   &lt;span class="na"&gt;resource_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_instance"&lt;/span&gt;
   &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instance_type"&lt;/span&gt;
   &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_equals"&lt;/span&gt;
   &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p5.48xlarge"&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cond_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attribute"&lt;/span&gt;
   &lt;span class="na"&gt;resource_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_instance"&lt;/span&gt;
   &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instance_type"&lt;/span&gt;
   &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_equals"&lt;/span&gt;
   &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p4d.24xlarge"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the ability to tailor policies to our specific needs, we'll next explore each tool's capacity to integrate broadly, determining how well they play with the rest of our tech stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration Capabilities
&lt;/h2&gt;

&lt;p&gt;Integration capabilities are the cornerstone of efficient DevOps practices.&lt;/p&gt;

&lt;p&gt;This section will evaluate how each static code analyzer enhances your tech stack through seamless integration with other systems and technologies.&lt;/p&gt;

&lt;p&gt;We will assess each tool against four key integration points that are vital for development workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker Image&lt;/strong&gt;: Ensures easy deployment across any container-supported environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDE Plugins&lt;/strong&gt;: Facilitates real-time feedback and improves code quality directly within the developer's workspace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Systems&lt;/strong&gt;: Supports direct integration through plugins or extensions, eliminating the need for manual downloads or CLI setups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit Hook&lt;/strong&gt;: Provides an early security checkpoint by scanning code before it is committed, catching errors at the initial stages.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Docker Image&lt;/th&gt;
&lt;th&gt;IDE Plugins&lt;/th&gt;
&lt;th&gt;CI/CD Systems&lt;/th&gt;
&lt;th&gt;Hook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KICKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode&lt;/td&gt;
&lt;td&gt;Github Actions, Terraform Cloud, Codefresh&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode, JetBrains, Vim&lt;/td&gt;
&lt;td&gt;Github Actions&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode, JetBrains, Vim&lt;/td&gt;
&lt;td&gt;Azure DevOps, GitHub Actions, Buildkite, Dagger, Semaphore, CircleCI, Concourse CI&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode&lt;/td&gt;
&lt;td&gt;GitHub Actions, Atlantis&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode, JetBrains&lt;/td&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;VSCode, JetBrains, Emacs, Vim&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In addition to the table above, here are a few noteworthy features of some tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checkov supports OpenAI integration to suggest remediations. But be careful because AI tends to hallucinate.&lt;/li&gt;
&lt;li&gt;KICS supports applying auto-remediation for some of its out-of-the-box policies. This also applies to custom policies, where you can define remediations and apply them automatically.&lt;/li&gt;
&lt;li&gt;Terrascan is the only one that provides the VSCode extension to create and test custom policies written in Rego.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having covered the integration capabilities, let’s now focus on the output formats each tool provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output Formats Provided
&lt;/h2&gt;

&lt;p&gt;Output formats extend the utility of static code analysis, facilitating integration with the CI/CD feedback loop and enabling its use as an artifact in subsequent CI jobs.&lt;/p&gt;

&lt;p&gt;This chapter examines the variety of formats each tool supports for this purpose.&lt;/p&gt;

&lt;p&gt;Each tool offers a range of output formats tailored to different needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For GitLab users&lt;/strong&gt;: For teams leveraging GitLab's security scanning, KICS, Checkov, and Semgrep OSS are equipped with compatible output formats, facilitating smooth GitLab integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For GitHub users&lt;/strong&gt;: SARIF's adoption as an industry standard, particularly by GitHub for code scanning, makes it a must-have. All tools assessed offer SARIF support, ensuring interoperability and broad utility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JUnit Reports&lt;/strong&gt;: The availability of JUnit output is crucial for capturing test results in a format recognizable by various CI systems. Trivy, Terrascan, Checkov, and Semgrep OSS support this, enabling clear visualization of test outcomes and enhancing the feedback loop within CI pipelines.&lt;/p&gt;

&lt;p&gt;Beyond these, each tool supports additional formats, enriching their application and versatility. Here's the full breakdown of the output formats, complementing the standard CLI output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Supported Output Formats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KICKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ASFF, CSV, Code Climate, CycloneDX, GItLab SAST, HTML, JSON, JUnit, PDF, SARIF, SonarQube&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Checkstyle, CSV, HTML, JSON, JUnit, Markdown, SARIF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ASFF, Cosign, CycloneDX, JSON, SARIF, SPDX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JSON, JUnit, SARIF, XML, YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CSV, CycloneDX, GItLab SAST, JSON, JUnit, SARIF, SPDX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Emacs, GitLab SAST, JSON, JUnit, SARIF, Vim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Moving from output formats to operational adaptability, let's investigate the customization options for scanner settings. This important feature allows each tool to align with varied project demands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing Scanner Settings
&lt;/h2&gt;

&lt;p&gt;This chapter moves beyond the default scanner settings and delves into scanner settings' customizability, ensuring that tools can be calibrated for any development environment or security requirement.&lt;/p&gt;

&lt;p&gt;I will evaluate each tool against criteria that define a tool's adaptability and user-friendliness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Targeted Scans&lt;/strong&gt;: Select specific directories for scanning or exclusion to focus on pertinent areas and skip irrelevant ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Code Ignore Policies&lt;/strong&gt;: Enable ignore directives within code to skip checks when exceptions apply selectively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity Thresholds&lt;/strong&gt;: Set reporting to include only findings above a chosen severity level, concentrating on the most impactful issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration File&lt;/strong&gt;: Employ configuration files for consistency and collaboration, enabling a 'configuration as code' approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TF Variables Interpolation&lt;/strong&gt;: Interpret and evaluate Terraform variables for an accurate security assessment of IaC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module Scanning&lt;/strong&gt;: For complete coverage, scans should include both local and remote (public/private) Terraform modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on these criteria, the following table offers a comparative view of how each tool performs, giving you a clear snapshot of their customization capabilities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Targeted Scans&lt;/th&gt;
&lt;th&gt;Ignore Policies&lt;/th&gt;
&lt;th&gt;Min Severity&lt;/th&gt;
&lt;th&gt;Config File&lt;/th&gt;
&lt;th&gt;Variables Interpolation&lt;/th&gt;
&lt;th&gt;Module Scanning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KICKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⁉️️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most reviewed tools meet nearly all the criteria set for scanner setting customization, demonstrating their flexibility and advanced capabilities. However, there are notable features worth considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KICS: Provides limited module scanning capabilities, restricted to some public modules from the Terraform registry, and does not cover local or private custom modules.&lt;/li&gt;
&lt;li&gt;Terrascan &amp;amp; Trivy: Both feature server modes that centralize vulnerability databases. This centralization facilitates a unified approach to applying policies and configurations, enhancing consistency and efficiency for teams and reducing the management overhead of diverse policies across multiple projects.&lt;/li&gt;
&lt;li&gt;Semgrep: It doesn't support scanner configuration files; instead, it uses the "config" word to call the rule sets and accepts such configs. Notably, it also does not support the scanning of Terraform modules at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Terraform Security Scanning: The Big Picture and Top Pick
&lt;/h2&gt;

&lt;p&gt;Here's a comprehensive comparison summary to guide your selection of the most suitable Terraform static code analyzer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Default Policies&lt;/th&gt;
&lt;th&gt;Custom Policies&lt;/th&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Output Formats&lt;/th&gt;
&lt;th&gt;Customization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KICKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;663&lt;/td&gt;
&lt;td&gt;OPA Rego&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ✅Git Hook&lt;/td&gt;
&lt;td&gt;ASFF, CSV, Code Climate, CycloneDX, GItLab SAST, HTML, JSON, JUnit, PDF, SARIF, SonarQube&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ✅Config File, ✅Variables Interpolation, ❌Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tfsec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;td&gt;OPA Rego&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ❌Git Hook&lt;/td&gt;
&lt;td&gt;Checkstyle, CSV, HTML, JSON, JUnit, Markdown, SARIF&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ✅Config File, ✅Variables Interpolation, ✅Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trivy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;322&lt;/td&gt;
&lt;td&gt;OPA Rego&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ❌Git Hook&lt;/td&gt;
&lt;td&gt;ASFF, Cosign, CycloneDX, JSON, SARIF, SPDX&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ✅Config File, ✅Variables Interpolation, ✅Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terrascan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;790&lt;/td&gt;
&lt;td&gt;OPA Rego&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ✅Git Hook&lt;/td&gt;
&lt;td&gt;JSON, JUnit, SARIF, XML, YAML&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ✅Config File, ✅Variables Interpolation, ✅Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2110&lt;/td&gt;
&lt;td&gt;YAML, Python&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ✅Git Hook&lt;/td&gt;
&lt;td&gt;CSV, CycloneDX, GItLab SAST, JSON, JUnit, SARIF, SPDX&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ✅Config File, ✅Variables Interpolation, ✅Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semgrep OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;362&lt;/td&gt;
&lt;td&gt;YAML&lt;/td&gt;
&lt;td&gt;✅Docker, ✅IDE, ✅CI/CD, ✅Git Hook&lt;/td&gt;
&lt;td&gt;Emacs, GitLab SAST, JSON, JUnit, SARIF, Vim&lt;/td&gt;
&lt;td&gt;✅Targeted Scans, ✅Ignore Policies, ✅Min Severity, ❌Config File, ✅Variables Interpolation, ❌Module Scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Integrating a Terraform security scanning into your development pipeline is a proven strategy to boost your security posture. These tools detect potential vulnerabilities early and enforce best practices and compliance standards, representing a proactive approach to infrastructure security.&lt;/p&gt;

&lt;p&gt;For teams not yet utilizing these tools, &lt;strong&gt;Checkov&lt;/strong&gt; is my top recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Biggest number of default policies and supported Terraform providers for a quick start.&lt;/li&gt;
&lt;li&gt;Custom policy support in YAML and Python for flexible policy creation.&lt;/li&gt;
&lt;li&gt;Wide integration options with Docker, IDEs, CI/CD systems, and Git Hooks for a smooth workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please share your favorite tool in the comments below! Also, let me know if I missed a cool product that should have been included in the review.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>security</category>
      <category>devops</category>
      <category>development</category>
    </item>
    <item>
      <title>Mastering AWS API Gateway V2 HTTP and AWS Lambda With Terraform</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Thu, 11 Jan 2024 15:18:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/mastering-aws-api-gateway-v2-http-and-aws-lambda-with-terraform-2d8d</link>
      <guid>https://dev.to/aws-builders/mastering-aws-api-gateway-v2-http-and-aws-lambda-with-terraform-2d8d</guid>
      <description>&lt;p&gt;With a solid foundation in AWS API Gateway and Lambda for serverless architecture, my recent deep dive into these cloud computing services felt like uncovering new layers in familiar territory. This article aims to be a comprehensive guide for developers and DevOps professionals looking to master serverless solutions using AWS and Terraform.&lt;/p&gt;

&lt;p&gt;The article provides an in-depth guide to combining AWS API Gateway V2 HTTP API (yes, this is the official name of that service 😄) and AWS Lambda services to implement a simple, robust, and cost-effective serverless back-end using Terraform.&lt;/p&gt;

&lt;p&gt;The journey was enlightening and engaging, especially as I were transforming these services into Infrastructure as Code. Through this article, I aim to share those moments of insight and the practical, hands-on tips that emerged from weaving these AWS services into a seamless, serverless architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigating the System Design: HTTP API Gateway and Lambda in Action
&lt;/h2&gt;

&lt;p&gt;Beginning our journey, we examine the complexities of serverless architecture, focusing on HTTP API and Lambda. A comprehensive system diagram will guide us as we analyze each component’s function and their collaborative roles in the larger infrastructure.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famw5ppbhu53u4cnb23wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famw5ppbhu53u4cnb23wa.png" alt="HTTP API Gateway and AWS Lambda flowchart" width="800" height="440"&gt;&lt;/a&gt;&lt;br&gt;
In our architecture, the HTTP API delegates access control to the Lambda function called “Authorizer”. This function stands as the gatekeeper, ensuring that only legitimate requests pass through to the underlying business logic.&lt;/p&gt;

&lt;p&gt;The HTTP API can have multiple routes (e.g., “/calendar,” “/meters,” and so on) and use different Authorizers per route or a single one for all of them. Clients that send their requests to the API must include specific identification information in their request header or query string. In this project, I go with a single authorizer to keep it simple.&lt;/p&gt;

&lt;p&gt;Upon receiving a request, the API service forwards a payload to the Authorizer containing metadata about the request, such as headers and query string components. The Authorizer processes this metadata (headers, in my case) to determine the request’s legitimacy.&lt;/p&gt;

&lt;p&gt;The decision, Allow or Deny, is passed back to the API, and if allowed, the API service then forwards the original request to the back-end, which, in this case, is implemented by additional Lambda functions. Otherwise, the client gets a response with a 403 status code, and the original request is not passed to the back-end.&lt;/p&gt;
&lt;h2&gt;
  
  
  Behind The Decision: Why Such a Setup?
&lt;/h2&gt;

&lt;p&gt;Choosing the right architectural setup is critical in balancing simplicity, cost-efficiency, and security. In this section, we uncover why integrating AWS HTTP API Gateway with Lambda Authorizer is a compelling choice, offering a streamlined approach without compromising security.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cost-Effectiveness: Balancing Performance and Price
&lt;/h3&gt;

&lt;p&gt;The AWS HTTP API is noteworthy for its streamlined and simple design compared to other API Gateway options. That translates directly into cost savings for businesses. Its efficiency makes it an ideal choice for cost-effective serverless computing, especially for those looking to optimize their cloud infrastructure with Terraform automation. Here is a more detailed comparison of different API Gateway options — &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/best-practices-api-gateway-private-apis-integration/cost-optimization.html" rel="noopener noreferrer"&gt;Cost optimization.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Security with Lambda Authorizer. This option means a Lambda function used for authorization, which is lean and efficient. It generally requires a bare minimum of resources. It executes quickly, particularly when configured with the ARM-based environment and 128M RAM allocation, costing $0,0000017 per second of running time, with $0.20 per 1M requests per month.&lt;/p&gt;

&lt;p&gt;💰 This pricing and performance combination are well-suited for rapid, lightweight authorizations. Together with AWS Lambda as a back-end, it makes a cost-effective solution. For example, if we add a few more Lambdas to back-end and assume that our setup receives 10000 requests per month, it would cost around $0.6 per month. Here is the link to detailed calculations — &lt;a href="https://calculator.aws/#/estimate?id=b1a8a473ab98ede32f5ca384c5e9487b967efafa" rel="noopener noreferrer"&gt;AWS Pricing Calculator&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Simplicity in Configuration: The Power of Header-Based Authorization
&lt;/h3&gt;

&lt;p&gt;A header-based authentication method facilitates straightforward client-server communication, often requiring less coding and resources to implement compared to more complex schemes.&lt;/p&gt;

&lt;p&gt;Although HTTP API offers stronger JWT-based authorization and mutual TLS authentication, header-based authorization remains a suitable choice for simpler applications that prioritize ease and quickness. By the way, there is also an option for IAM-based authorization whose core idea is the “private API” or internal usage of the API (e.g., solely inside the VPC, no internet), but with “&lt;a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html" rel="noopener noreferrer"&gt;IAM Anywhere&lt;/a&gt;,” this can be expanded to practically anywhere. 😁&lt;/p&gt;

&lt;p&gt;This architecture suits applications requiring rapid development and deployment without complex authorization mechanisms. It’s ideal for small to medium-sized serverless applications or specific use cases in larger systems where quick, cost-effective, and secure access to APIs is a priority.&lt;/p&gt;

&lt;p&gt;💡 Imagine a retail company wanting to manage its inventory efficiently. By leveraging AWS API Gateway and Lambda, they can develop a system where each item’s RFID tags are scanned and processed through an API endpoint. When a product is moved or sold, its status is updated in real-time in the database, facilitated by Lambda functions. This serverless architecture ensures high availability and scalability and significantly reduces operational costs, a crucial factor for the highly competitive retail industry. This example showcases how our serverless setup can be effectively utilized in retail for streamlined inventory tracking and management.&lt;/p&gt;
&lt;h2&gt;
  
  
  Exploring AWS Lambda: Features and Integration
&lt;/h2&gt;

&lt;p&gt;Diving into AWS Lambda, this section explores its features and indispensable role within the serverless infrastructure. We will unravel the complexities of Lambda functions and examine the practicalities of deploying and managing these functions within the project.&lt;/p&gt;
&lt;h3&gt;
  
  
  AWS Lambda Runtime and Deployment Model
&lt;/h3&gt;

&lt;p&gt;🚀 Choosing the AWS &lt;strong&gt;Lambda runtime arm64&lt;/strong&gt;, combined with the &lt;strong&gt;OS-only runtime based on Amazon Linux 2023&lt;/strong&gt;, strategically boosts cost efficiency and performance. This choice aligns with the best practices for serverless computing in AWS, offering an optimal solution for those seeking to leverage AWS services for scalable cloud solutions.&lt;/p&gt;

&lt;p&gt;Particularly effective for Go-based functions, this runtime configuration is lean yet powerful. For applications in other languages, delving into &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;language-specific runtimes based on AL 2023&lt;/a&gt; can also leverage the latest efficiencies of AWS-managed operating systems.&lt;/p&gt;

&lt;p&gt;I also welcome you to read this benchmarking analysis to get more insights about the ARM-based environment for AWS Lambda — &lt;a href="https://aws.amazon.com/blogs/apn/comparing-aws-lambda-arm-vs-x86-performance-cost-and-analysis-2/" rel="noopener noreferrer"&gt;Comparing AWS Lambda Arm vs. x86 Performance, Cost, and Analysis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;.zip deployment&lt;/strong&gt; model is chosen for its simplicity, avoiding additional management of the image registry (ECR) and Docker images. Also, AWS automatically patches .zip functions for the latest runtime security and bug fixes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Efficient Terraform Coding for AWS Lambda
&lt;/h3&gt;

&lt;p&gt;In our architecture, AWS Lambda functions serve dual purposes — as an authentication gatekeeper and a robust back-end for business logic. Despite varying code across functions, their configurations share much of similarities.&lt;/p&gt;

&lt;p&gt;By adhering to the DRY (Don't Repeat Yourself) principle, I have crafted a Terraform module to streamline the management of Lambda functions and their dependencies. This approach ensures maintainable and scalable infrastructure. The module's structure is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aws_lambda_function&lt;/code&gt; — to describe the core configuration of the function&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_iam_role&lt;/code&gt; + &lt;code&gt;aws_iam_role_policy&lt;/code&gt; + &lt;code&gt;aws_iam_policy_document&lt;/code&gt; — to manage the access from Lambda to other resources (e.g., SSM Parameter Store)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_cloudwatch_log_group&lt;/code&gt; — to keep the execution logs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_ssm_parameter&lt;/code&gt; — to store sensitive information (e.g., secrets) and other configurations that we should keep separate from the source code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This Terraform module implements a project-specific use case for Lambda functions. However, if you're seeking for a generic all-in-one module for AWS Lambda, I recommend checking out this one — &lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest" rel="noopener noreferrer"&gt;Terraform AWS Lambda Module&lt;/a&gt; by Anton Babenko. &lt;/p&gt;

&lt;p&gt;To efficiently develop Terraform code for Lambda functions, use the following techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use local values, expressions, and variables to implement consistent naming across different resources logically grouped by a module or project;&lt;/li&gt;
&lt;li&gt;Use function environment variables to connect the code with SSM Parameter Store parameters or Secrets Manager secrets to protect sensitive data like tokens or credentials;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;for_each&lt;/code&gt; meta-argument and &lt;code&gt;for&lt;/code&gt; expression to reduce the amount of code and automate the configuration for resources of the same type (e.g., &lt;code&gt;ssm_parameter&lt;/code&gt;) or code blocks within a resource.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is a practical example illustrating these Terraform strategies in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;full_function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_function_name&lt;/span&gt;  
  &lt;span class="nx"&gt;role&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;  
  &lt;span class="nx"&gt;architectures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arm64"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
  &lt;span class="nx"&gt;filename&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deployment_file&lt;/span&gt;  
  &lt;span class="nx"&gt;package_type&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Zip"&lt;/span&gt;  
  &lt;span class="nx"&gt;runtime&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"provided.al2023"&lt;/span&gt;  
  &lt;span class="nx"&gt;handler&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bootstrap.handler"&lt;/span&gt;  
  &lt;span class="nx"&gt;timeout&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_timeout&lt;/span&gt;  
  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_ssm_parameter_names&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;aws_ssm_parameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_ssm_parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_parameter"&lt;/span&gt; &lt;span class="s2"&gt;"function_ssm_parameters"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_ssm_parameter_names&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/projects/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/lambda/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
  &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SecureString"&lt;/span&gt;  
  &lt;span class="nx"&gt;key_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_kms_alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;  
  &lt;span class="nx"&gt;value&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;  
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
      &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;]&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_log_group"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/aws/lambda/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_function_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
  &lt;span class="nx"&gt;log_group_class&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;  
  &lt;span class="nx"&gt;retention_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The complete terraform module code is available in the &lt;a href="https://github.com/vasylenko/inkyframe/blob/main/infra/modules/lambda/main.tf" rel="noopener noreferrer"&gt;project repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this Terraform code, I deliberately hardcoded specific arguments for an optimal Lambda runtime configuration, ensuring efficiency and performance.&lt;/p&gt;

&lt;p&gt;Then variables and local values, set only once, implement a naming convention for all resource arguments, making it easy to understand the infrastructure and change the naming and attributes later.&lt;/p&gt;

&lt;p&gt;Lambda's environment variables and corresponding SSM parameters coexist effectively with the help of &lt;code&gt;for_each&lt;/code&gt; and &lt;code&gt;for&lt;/code&gt;. I used the &lt;code&gt;for_each&lt;/code&gt; meta-argument to dynamically create SSM Parameter resources and the &lt;code&gt;for&lt;/code&gt; expression to configure environment variables in AWS Lambda. This also means that if the &lt;code&gt;function_ssm_parameter_names&lt;/code&gt; variable value is not provided, then Terraform does not create either SSM parameter resources or the environment code block inside the Lambda resource because the default value of that variable is an empty set.&lt;/p&gt;

&lt;p&gt;By the way, I have another blog post that explains several techniques to enhance your Terraform proficiency — &lt;a href="https://devdosvid.blog/2022/01/16/some-techniques-to-enhance-your-terraform-proficiency/?utm_medium=social&amp;amp;utm_source=devto&amp;amp;utm_campaign=blog-promo" rel="noopener noreferrer"&gt;check it out&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoking Lambda: Permissions and Resource-Based Policies
&lt;/h3&gt;

&lt;p&gt;Configured with just a few input variables, the Terraform module efficiently outputs the &lt;code&gt;aws_lambda_function&lt;/code&gt; resource. This streamlined output is then adeptly used to facilitate subsequent configurations within the HTTP API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"lambda_api_gw_authorizer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;source&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/lambda"&lt;/span&gt;  
  &lt;span class="nx"&gt;deployment_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../backend/lambda-apigw-authorizer/deployment.zip"&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api-gateway-authorizer"&lt;/span&gt;  
  &lt;span class="nx"&gt;project_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;  
  &lt;span class="nx"&gt;function_ssm_parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
    &lt;span class="s2"&gt;"authorization-token"&lt;/span&gt;  
  &lt;span class="p"&gt;]&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"lambda_calendar_backend"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;source&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/lambda"&lt;/span&gt;  
  &lt;span class="nx"&gt;deployment_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../backend/lambda-calendar-backend/deployment.zip"&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"calendar-backend"&lt;/span&gt;  
  &lt;span class="nx"&gt;project_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;  
  &lt;span class="nx"&gt;function_ssm_parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
    &lt;span class="s2"&gt;"google-api-oauth-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="s2"&gt;"google-api-credentials"&lt;/span&gt;  
  &lt;span class="p"&gt;]&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As an example of module output usage, here is the configuration of &lt;code&gt;aws_lambda_permissions&lt;/code&gt; resource that I use outside the AWS Lambda module to allow the HTTP API service to invoke the function used as Authorizer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"allow_api_gw_invoke_authorizer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"allowInvokeFromAPIGatewayAuthorizer"&lt;/span&gt;  
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_api_gw_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;  
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;  
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/authorizers/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header_based_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda resource-based policy combines the trust and permission policies, and provides a simple yet efficient way to grant other AWS services or principals the ability to invoke Lambda functions. It is important to note that for an API to invoke a function, Lambda requires its &lt;strong&gt;execution&lt;/strong&gt; ARN, not the resource ARN.&lt;/p&gt;

&lt;p&gt;As a side note, check out this &lt;a href="https://docs.aws.amazon.com/lambda/latest/operatorguide/intro.html" rel="noopener noreferrer"&gt;AWS Lambda Operator Guide&lt;/a&gt;, which offers specialized advice on developing, securing, and monitoring applications based on AWS Lambda.&lt;/p&gt;

&lt;p&gt;Let's switch to the HTTP API part to see how it looks and learn how it integrates Lambda functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive into HTTP API Gateway
&lt;/h2&gt;

&lt;p&gt;Now, we focus on the HTTP API Gateway, delving into its essential concepts, seamless integration with AWS Lambda, and using Terraform efficiently for streamlined configuration.&lt;/p&gt;

&lt;p&gt;But before we do that, and since we have partially covered the Terraform code already, I'd like to illustrate the logical connection between three main components of the project's Terraform codebase: AWS Lambda, HTTP API, and API Routes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9d3sv28obn3kgz6h7j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9d3sv28obn3kgz6h7j6.png" alt="AWS Lambda and HTTP API Terraform integration diagram" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will explain the API Route module in detail a bit later, but for now, for the broader context, here is what happens inside Terraform:&lt;br&gt;
AWS HTTP API code logically represents the "global" (within a project) set of resources and uses the function created by the Lambda Terraform module for the Authorizer configuration. Meanwhile, the API Route Terraform module configures specific routes for the HTTP API (hence, requires some info from it) with integration to back-ends implemented by Lambdas (hence, requires some info from them, too).&lt;/p&gt;

&lt;p&gt;Back to HTTP API overview. The following &lt;strong&gt;components of the HTTP API&lt;/strong&gt; constitute its backbone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt; — a combination of the HTTP method (e.g., GET or POST) with the API route (e.g., /meters). For example: "POST /meters". Routes can optionally use &lt;strong&gt;Authorizers&lt;/strong&gt; — a mechanism to control access to the HTTP API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration&lt;/strong&gt; — the technical and logical connection between the Route and one of the supported back-end resources. For example, with AWS Lambda integration, API Gateway sends the entire request as input to a back-end Lambda function and then transforms the Lambda function output to a front-end HTTP response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage and Deployment&lt;/strong&gt; — A stage serves as a designated reference to a deployment, essentially capturing a snapshot of the API at a certain point. It's employed to control and optimize a specific deployment version. For instance, stage configurations can be adjusted to tailor request throttling, set up logging, or establish stage variables to be used by API (if needed).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Implementing AWS API Gateway V2 HTTP API with Terraform
&lt;/h3&gt;

&lt;p&gt;Below, I detail the Terraform resources essential for implementing the HTTP API, ensuring a transparent and effective setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aws_apigatewayv2_api&lt;/code&gt; — the HTTP API itself;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_apigatewayv2_route&lt;/code&gt; — the Route for the API that must specify the integration target (e.g., Lambda) and, optionally, the Authorizer;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_apigatewayv2_authorizer&lt;/code&gt; — the Authorizer to use for Routes;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_apigatewayv2_integration&lt;/code&gt; — the resource that specifies the back-end where the API sends the requests (e.g., AWS Lambda);&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_lambda_permission&lt;/code&gt; — the resource-based policy for AWS Lambda to allow the invocations from the API;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_apigatewayv2_stage&lt;/code&gt; — the name of the Stage that references the Deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Applying Terraform for HTTP API Gateway and Lambda Authorizer
&lt;/h3&gt;

&lt;p&gt;The HTTP API is the simplest in the API Gateway family (so far), so its Terraform resource has relatively few configuration options, most of which can be left at their default values.&lt;/p&gt;

&lt;p&gt;As for the Authorizer, it can have two options for letting API know its decision: &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response" rel="noopener noreferrer"&gt;simple response and IAM policy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;simple response&lt;/strong&gt; just returns a Boolean value to indicate whether the API should allow the request (True) or forbid it (False).&lt;/p&gt;

&lt;p&gt;The IAM policy option is customizable and allows crafting custom policy statements that allow granular access to explicitly provided resources.&lt;/p&gt;

&lt;p&gt;In this project, I follow the way of simplicity and use the "simple response", so the response from Lambda Authorizer to HTTP API looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isAuthorized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's review the HTTP API resource along with the API Authorizer that I used for all routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_api"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;  
  &lt;span class="nx"&gt;protocol_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_authorizer"&lt;/span&gt; &lt;span class="s2"&gt;"header_based_authorizer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;api_id&lt;/span&gt;                            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;  
  &lt;span class="nx"&gt;authorizer_type&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"REQUEST"&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;                              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"header-based-authorizer"&lt;/span&gt;  
  &lt;span class="nx"&gt;authorizer_payload_format_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.0"&lt;/span&gt;  
  &lt;span class="nx"&gt;authorizer_uri&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_api_gw_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke_arn&lt;/span&gt;  
  &lt;span class="nx"&gt;enable_simple_responses&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  
  &lt;span class="nx"&gt;identity_sources&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;request.header.authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="nx"&gt;authorizer_result_ttl_in_seconds&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"allow_api_gw_invoke_authorizer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"allowInvokeFromAPIGatewayAuthorizer"&lt;/span&gt;  
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_api_gw_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;  
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;  
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/authorizers/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header_based_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The complete code is available in the &lt;a href="https://github.com/vasylenko/inkyframe/blob/main/infra/apigateway.tf" rel="noopener noreferrer"&gt;project repository&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Consider the following key points when Terraforming this part.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;identity_sources&lt;/code&gt; argument of the &lt;code&gt;aws_apigatewayv2_authorizer&lt;/code&gt; resource: This is where I defined what exactly the Authorizer should validate. I used the header named &lt;code&gt;authorization&lt;/code&gt; so the Authorizer Lambda function would check its value to decide whether to authorize the request.\&lt;br&gt;
💡 &lt;em&gt;Check out other options available to use as the identity source — &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.identity-sources" rel="noopener noreferrer"&gt;Identity sources&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;authorizer_uri&lt;/code&gt; argument of the &lt;code&gt;aws_apigatewayv2_authorizer&lt;/code&gt; resource: It is the &lt;strong&gt;invocation&lt;/strong&gt; ARN of the Lambda function used as Authorizer (not the Lambda's resource ARN).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;authorizer_result_ttl_in_seconds&lt;/code&gt; argument of the &lt;code&gt;aws_apigatewayv2_authorizer&lt;/code&gt; resource: This allows to skip the Authorizer invocation for the given time if a client provided the same identity source values (e.g., authorization header).&lt;/p&gt;

&lt;p&gt;AWS API Gateway HTTP can employ the identity sources as the cache key to preserve the authorization results for a while. Should a client provide identical parameters in identity sources within the preset TTL duration, API Gateway will retrieve the result from the cached authorizer instead of calling upon it again. This helps save a lot on AWS Lambda Authorizer invocations and works great with simple scenarios. However, it might be cumbersome if you need severral custom authorization responses per function or if you use custom IAM policies instead of the "simple response" option.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;source_arn&lt;/code&gt; argument of &lt;code&gt;aws_lambda_permission&lt;/code&gt;: Similar to the &lt;code&gt;authorizer_uri&lt;/code&gt; argument, this one expects the &lt;strong&gt;execution&lt;/strong&gt; ARN of the HTTP API followed by the Authorizer identifier.&lt;/p&gt;

&lt;p&gt;Now, let's see how Routes are codified with Terraform.&lt;/p&gt;
&lt;h3&gt;
  
  
  Applying Terraform for HTTP API Routes
&lt;/h3&gt;

&lt;p&gt;💡 Because an API typically has multiple routes, creating another Terraform module that implements the configurable HTTP API Gateway route is beneficial. Hence, the &lt;code&gt;aws_apigatewayv2_route&lt;/code&gt;, &lt;code&gt;aws_apigatewayv2_integration&lt;/code&gt;, and &lt;code&gt;aws_lambda_permission&lt;/code&gt; resources would constitute such a module.&lt;/p&gt;

&lt;p&gt;This Terraform module implements a specific use case for HTTP API Gateway. However, if you're seeking for a generic all-in-one module for API Gateway, I recommend checking out this one — &lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/apigateway-v2/aws/latest" rel="noopener noreferrer"&gt;Terraform AWS API Gateway Module&lt;/a&gt; by Anton Babenko.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_route"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;api_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_id&lt;/span&gt;  
  &lt;span class="nx"&gt;route_key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route_key&lt;/span&gt;  
  &lt;span class="nx"&gt;authorization_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CUSTOM"&lt;/span&gt;  
  &lt;span class="nx"&gt;authorizer_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorizer_id&lt;/span&gt;  
  &lt;span class="nx"&gt;target&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"integrations/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_integration"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;api_id&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_id&lt;/span&gt;  
  &lt;span class="nx"&gt;integration_type&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS_PROXY"&lt;/span&gt;  
  &lt;span class="nx"&gt;connection_type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INTERNET"&lt;/span&gt;  
  &lt;span class="nx"&gt;integration_uri&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_invocation_arn&lt;/span&gt;  
  &lt;span class="nx"&gt;payload_format_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.0"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"allowInvokeFromAPIGatewayRoute"&lt;/span&gt;  
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;  
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_name&lt;/span&gt;  
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;  
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_gw_execution_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*/*/*/*"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, I want to highlight several key aspects for understanding the resources' arguments within that module.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;target&lt;/code&gt; argument of the &lt;code&gt;aws_apigatewayv2_route&lt;/code&gt; resource implies that the integration ID should be prefixed with the "&lt;code&gt;integrations/&lt;/code&gt;" keyword.&lt;/p&gt;

&lt;p&gt;While the &lt;code&gt;connection_type&lt;/code&gt; argument of the &lt;code&gt;aws_apigatewayv2_integration&lt;/code&gt; resource specifies "INTERNET", it does not mean that the Lambda function must have the publicly available URL. This value must be used unless you work with a VPC endpoint for API Gateway for internal usage.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;source_arn&lt;/code&gt; argument in the &lt;code&gt;aws_lambda_permission&lt;/code&gt; resource, similar to earlier, it requires the &lt;strong&gt;execution&lt;/strong&gt; ARN of the API. However, this time, it is the integration of the HTTP API Route with Lambda. And the ARN format of this one is different and a bit tricky:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:partition:execute-api:region:account-id:api-id/stage/http-method/resource-path&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;arn:partition:execute-api:region:account-id:api-id&lt;/code&gt; part constitutes the execution ARN of the HTTP API itself, so for the sake of simplicity, I decided to go with wildcards after it.\&lt;br&gt;
&lt;em&gt;For your convenience, here is the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/arn-format-reference.html" rel="noopener noreferrer"&gt;detailed specification&lt;/a&gt; of API Gateway ARNs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The HTTP API Route module expects several input variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authorizer_id&lt;/code&gt; — the identifier of the Authorizer to use on this route;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;route_key&lt;/code&gt; — the route key for the route, e.g., &lt;code&gt;GET /foo/bar&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api_id&lt;/code&gt; — the identifier of HTTP API created earlier;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lambda_invocation_arn&lt;/code&gt; — the Invocation ARN of the Lambda function;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lambda_function_name&lt;/code&gt; — the name of the Lambda function to integrate with the route;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api_gw_execution_arn&lt;/code&gt; — the Execution ARN of the HTTP API that invokes a Lambda function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a closer look on API Gateway V2 HTTP API route.&lt;/p&gt;

&lt;p&gt;A route consists of an HTTP method and a resource path with an optional variable. Based on the pre-defined convention, it uses a simplified routing configuration and methods request model (comparable to other APIs).&lt;/p&gt;

&lt;p&gt;While I was working with the HTTP API, I found this simplified approach to be great because it allows easy access to the request context from AWS Lambda functions, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A path variable in a route, e.g., &lt;code&gt;GET /calendars/{calendar-name}&lt;/code&gt;, would be available for the integrated AWS Lambda by its name inside the pathParameter JSON field, e.g.,  &lt;code&gt;pathParamters.calendar-name&lt;/code&gt;, of the event object sent by API to Lambda. In other words, you do not need to explicitly set the mapping between the path variable and its representation to the back-end.&lt;/li&gt;
&lt;li&gt;A request query string is parsed into separate parameter-value pairs and available in the &lt;code&gt;queryStringParameters&lt;/code&gt; field of the event object sent by API to Lambda. Again, without the explicit mapping configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, you can read more about the Route specification of HTTP API and how to transform requests and responses from the API side if you need to adjust something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html" rel="noopener noreferrer"&gt;Working with routes for HTTP APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html" rel="noopener noreferrer"&gt;Transforming API requests and responses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now back to Terraform. Below is the code snippet that illustrates the call of the API Route Terraform module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"route_calendars"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;source&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/api-gateway-route"&lt;/span&gt;  
  &lt;span class="nx"&gt;api_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;  
  &lt;span class="nx"&gt;route_key&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GET /calendars/{calendar-name}"&lt;/span&gt;  
  &lt;span class="nx"&gt;api_gw_execution_arn&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution_arn&lt;/span&gt;  
  &lt;span class="nx"&gt;lambda_invocation_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_calendar_backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke_arn&lt;/span&gt;  
  &lt;span class="nx"&gt;lambda_function_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_calendar_backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;  
  &lt;span class="nx"&gt;authorizer_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header_based_authorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module logically relies on both the HTTP API and Lambda resources to configure their integration by implementing the Route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing Security and Monitoring of AWS API Gateway V2 HTTP API
&lt;/h2&gt;

&lt;p&gt;Several additional options are available to monitor and protect the HTTP API: logs, metrics, and throttling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview of HTTP API monitoring and protection options
&lt;/h3&gt;

&lt;p&gt;Logging, metrics, and throttling are configured on the Stage level but allow configuration granularity for the Routes.&lt;/p&gt;

&lt;p&gt;For logs, you can configure the CloudWatch log group, the log format (JSON, CLF, XML, CSV), and content filters. The &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging-variables.html" rel="noopener noreferrer"&gt;logging variables&lt;/a&gt; allow you to customize the information that appears in logs. I will provide an example of such a configuration later in the article.&lt;/p&gt;

&lt;p&gt;By default, API Gateway sends only API and stage-level metrics to CloudWatch in one-minute periods. However, you can enable &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-metrics.html" rel="noopener noreferrer"&gt;detailed metrics&lt;/a&gt; and additionally collect the per-route metrics.&lt;/p&gt;

&lt;p&gt;To safeguard your HTTP API from excessive requests, you can employ throttling settings, which allow you to set limits per individual route as well as for all routes collectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring monitoring and protection for HTTP API with Terraform
&lt;/h3&gt;

&lt;p&gt;Now, let's see how Terraform helps configure the protection and monitoring for HTTP API.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, API Gateway applies these configurations at the Stage level, which is why the aws_apigatewayv2_stage resource encapsulates them all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_stage"&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;api_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;default"&lt;/span&gt;  
  &lt;span class="nx"&gt;auto_deploy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Default stage (i.e., Production mode)"&lt;/span&gt;  
  &lt;span class="nx"&gt;default_route_settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nx"&gt;throttling_burst_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  
    &lt;span class="nx"&gt;throttling_rate_limit&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="nx"&gt;access_log_settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nx"&gt;destination_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_log_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_gateway_logs_inkyframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;  
    &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
      &lt;span class="nx"&gt;authorizerError&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.authorizer.error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;identitySourceIP&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.identity.sourceIp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationError&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationErrorMessage&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.errorMessage"&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationLatency&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.latency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationRequestId&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.requestId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationStatus&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.integrationStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;integrationStatusCode&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.integration.status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;requestErrorMessage&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.error.message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;requestErrorMessageString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.error.messageString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;requestId&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.requestId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nx"&gt;routeKey&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;context.routeKey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;})&lt;/span&gt;   
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I applied the default &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-throttling.html" rel="noopener noreferrer"&gt;throttling settings&lt;/a&gt;: for my project, 1 request per second was enough at that point.&lt;/p&gt;

&lt;p&gt;🤔 There is a nuance, though, that makes Terraforming API Gateway a little inconvenient — the IAM role that allows API to write logs must be defined on a region level. Therefore, if you maintain several Terraform projects for the same AWS account, you might need to have the following configuration stand separately to avoid conflicts or misunderstandings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_account"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;cloudwatch_role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_gateway_cloudwatch_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"api_gateway_cloudwatch_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api-gateway-cloudwatch-logs"&lt;/span&gt;  
  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;  
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
      &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;  
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
          &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;  
      &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;]&lt;/span&gt;  
  &lt;span class="p"&gt;})&lt;/span&gt;  
  &lt;span class="nx"&gt;managed_policy_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_log_group"&lt;/span&gt; &lt;span class="s2"&gt;"api_gateway_logs_inkyframe"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/aws/apigateway/inkyframe"&lt;/span&gt;  
  &lt;span class="nx"&gt;log_group_class&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;  
  &lt;span class="nx"&gt;retention_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one more thing about HTTP API deployments and stages. I use the special &lt;code&gt;$default&lt;/code&gt; keyword to have a single stage (hence, the default one), and I also used automatic deployments: with any change made to API configuration, AWS will automatically generate a new Deployment and bound it with the Stage. If you prefer controlling deployments manually, there is a special resource exists that implements this — &lt;code&gt;aws_apigatewayv2_deployment&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_apigatewayv2_deployment"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Example deployment"&lt;/span&gt;

  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;redeployment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;","&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case, the &lt;code&gt;aws_apigatewayv2_stage&lt;/code&gt; resource requires the &lt;code&gt;deployment_id&lt;/code&gt; argument to link itself with a particular Deployment and, therefore, represent the state of the API configuration.&lt;/p&gt;

&lt;p&gt;Also, API Gateway requires at least one configured API Route before the deployment is initiated/created. However, these resources do not explicitly depend on each other via attribute references. To avoid the race condition in Terraform, you need to reference the Route resource in the &lt;code&gt;aws_apigatewayv2_deployment&lt;/code&gt; resource via the &lt;code&gt;triggers&lt;/code&gt; argument (as shown above) or via the &lt;code&gt;depends_on&lt;/code&gt; meta-argument. Otherwise, Terraform will try to apply changes to both resources simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterword: Simplifying Serverless Architectures
&lt;/h2&gt;

&lt;p&gt;In wrapping up our exploration of AWS HTTP API Gateway, AWS Lambda, and Terraform, we've delved into how these powerful tools work in tandem to streamline and enhance serverless architectures. This article aimed to combine my experience with new knowledge and demystify the complexities of used services, showcasing their capabilities in creating efficient, cost-effective solutions for modern cloud-based applications.&lt;/p&gt;

&lt;p&gt;We focused on practical implementation and the tangible benefits of combining these technologies. By leveraging Terraform, we've seen how infrastructure management can be simplified, allowing for clearer, more maintainable code. The combination of AWS Lambda and HTTP API Gateway has demonstrated the efficiency of serverless computing, offering scalability and performance without the burden of extensive configuration and management.&lt;/p&gt;

&lt;p&gt;This exploration underlines the importance of choosing the right tools and strategies in cloud computing. It reminds developers and architects that creating robust and efficient serverless systems is within reach with a thoughtful approach and the right set of tools. As the cloud landscape continues to evolve, staying informed and adaptable is key to harnessing the full potential of these technologies. 💚&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Hello terraform_data! Goodbye Null Resource!</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Wed, 19 Apr 2023 00:17:47 +0000</pubDate>
      <link>https://dev.to/aws-builders/hello-terraformdata-goodbye-nullresource-3g21</link>
      <guid>https://dev.to/aws-builders/hello-terraformdata-goodbye-nullresource-3g21</guid>
      <description>&lt;p&gt;&lt;strong&gt;Terraform&lt;/strong&gt; version &lt;strong&gt;1.4&lt;/strong&gt; was recently released and brought a range of new features, including improved run output in Terraform Cloud, the ability to use OPA policy results in the CLI, and a built-in alternative to the null resource — terraform_data.&lt;/p&gt;

&lt;p&gt;In this blog post, I want to demonstrate and explain the &lt;strong&gt;terraform_data&lt;/strong&gt; resource that serves two purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;firstly, it allows arbitrary values to be stored and used afterward to implement lifecycle triggers of other resources&lt;/li&gt;
&lt;li&gt;secondly, it can be used to trigger provisioners when there isn't a more appropriate managed resource available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those of you, who are familiar with the null provider, the &lt;code&gt;terraform_data&lt;/code&gt; resource might look very similar. And you're right!\&lt;br&gt;
Rather than being a separate provider, the terraform_data managed resource now offers the same capabilities as an integrated feature. Pretty cool! \&lt;br&gt;
While the null provider is still available and there are no statements about its deprecation thus far (&lt;a href="https://registry.terraform.io/providers/hashicorp/null/3.2.1/docs" rel="noopener noreferrer"&gt;as of April 2023, v3.2.1&lt;/a&gt;), the  &lt;code&gt;terraform_data&lt;/code&gt; is the native replacement of the &lt;code&gt;null_resource&lt;/code&gt;, and the latter might soon become deprecated.&lt;/p&gt;

&lt;p&gt;The  &lt;code&gt;terraform_data&lt;/code&gt; resource has two optional arguments, &lt;strong&gt;input&lt;/strong&gt; and &lt;strong&gt;triggers_replace&lt;/strong&gt;, and its configuration looks as follows:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusn4xbmsqcxlfg48aw18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusn4xbmsqcxlfg48aw18.png" alt="terraform data resource arguments" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;input&lt;/code&gt; (optional) stores the value that is passed to the resource&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;triggers_replace&lt;/code&gt; (optional) is a value that triggers resource replacement when changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two attributes, in addition to the arguments, which are stored in the state: &lt;strong&gt;id&lt;/strong&gt; and &lt;strong&gt;output&lt;/strong&gt; after the resource is created. Let's take a look:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafhv1g1aa9zd61kpnn4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafhv1g1aa9zd61kpnn4d.png" alt="terraform data resource attributes" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The  &lt;code&gt;output&lt;/code&gt; attribute is computed based on the value of the &lt;code&gt;input&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;id&lt;/code&gt; is just a unique value of the resource instance in the state (as for any other resource).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use case for terraform_data with replace_triggered_by
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the first use case for the terraform_data resource. It is the ability to trigger resource replacement based on the value of the input argument.&lt;/p&gt;

&lt;p&gt;A bit of context here: the &lt;strong&gt;replace_triggered_by&lt;/strong&gt; argument of the resource lifecycle meta-argument allows you to trigger resource replacement based on another referenced resource or its attribute. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are not yet familiar with the &lt;code&gt;replace_triggered_by&lt;/code&gt;, you can check &lt;a href="https://devdosvid.blog/2022/05/04/new-lifecycle-options-and-refactoring-capabilities-in-terraform-1-1-and-1-2/#trigger-resource-replacement-with-replace_triggered_by" rel="noopener noreferrer"&gt;another blog post that explains it&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The replace_triggered_by is a powerful feature, but here is the thing about it: only a resource or its attribute must be specified, and &lt;strong&gt;you cannot use a variable or a local value for replace_triggered_by&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But with terraform_data, you can indirectly initiate another resource replacement by using either a variable or an expression within a local value for the &lt;code&gt;input&lt;/code&gt; argument.&lt;/p&gt;

&lt;p&gt;Let me give you an example here. Consider the following code:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeeu4vzq6komopc4x75b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeeu4vzq6komopc4x75b.png" alt="trigger replacement based on input variable" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By modifying the  &lt;code&gt;revision&lt;/code&gt; variable, the next Terraform plan will suggest a replacement action against aws_instance.webserver:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F132ldl5cmplem7vf5lcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F132ldl5cmplem7vf5lcu.png" alt="terraform_data with replace_triggered_by" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case for terraform_data with provisioner
&lt;/h2&gt;

&lt;p&gt;Before we start: HashiCorp suggests (and I also support that) avoiding provisioner usage unless you have no other options left. One of the reasons — additional, implicit, and unobvious dependency that appears in the codebase — the binary, which is called inside the provisioner block, must be present on the machine. \&lt;br&gt;
But let's be real, the provisioner feature is still kicking, and there's always that one unique project that needs it.&lt;/p&gt;

&lt;p&gt;Here is the code snippet that demonstrates the usage of the provisioner within the terraform_data resource: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2p1gpw1utib5bhq2b3p7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2p1gpw1utib5bhq2b3p7.png" alt="terraform_data with provisioner" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the following happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When resources are created the first time, the provisioner inside &lt;code&gt;terraform_data&lt;/code&gt; runs&lt;/li&gt;
&lt;li&gt;Sequential plan/apply will trigger another execution of the provisioner only when the private IP of the instance (aws_instance.webserver.private_ip) changes because that will trigger &lt;code&gt;terraform_data&lt;/code&gt; recreation. At the same time, no changes to the internal IP mean no provisioner execution.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;With its ability to store and use values for lifecycle triggers and provisioners, &lt;strong&gt;terraform_data&lt;/strong&gt; is a powerful tool that can enhance your Terraform configuration. &lt;/p&gt;

&lt;p&gt;Although the null provider still has its place in the Terraform ecosystem, terraform_data is its evolution, and its integration as a feature is certainly something to be excited about. &lt;/p&gt;

&lt;p&gt;Why not give it a try in your next project and see how it can simplify your infrastructure as code workflows? Keep on coding! 🙌&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>news</category>
      <category>programming</category>
    </item>
    <item>
      <title>Amazon VPC Lattice — Build Applications, Not Networks</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Fri, 17 Mar 2023 23:33:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/amazon-vpc-lattice-build-applications-not-networks-59j8</link>
      <guid>https://dev.to/aws-builders/amazon-vpc-lattice-build-applications-not-networks-59j8</guid>
      <description>&lt;p&gt;Last year's re:Invent brought a lot of amazing updates to the big family of AWS services. In this blog post, I would like to explain one of such new offerings — &lt;strong&gt;Amazon VPC Lattice&lt;/strong&gt; — an exciting new service that simplifies the networking layer for developers and cloud administrators.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Lattice
&lt;/h2&gt;

&lt;p&gt;So what exactly is Amazon VPC Lattice? It is an application layer networking service that enables consistent and secure service-to-service communication without the need for prior networking expertise. With VPC Lattice, you can easily configure network access, traffic management, and network monitoring, making service-to-service communication seamless across VPCs and accounts, irrespective of the underlying compute type.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it helps
&lt;/h2&gt;

&lt;p&gt;VPC Lattice helps address several use cases, including connecting services at scale, implementing granular access permissions, advanced traffic controls, and observing service-to-service interactions. The service offers connectivity over HTTP/HTTPS and gRPC protocols through a dedicated data plane within VPC. Administrators can use AWS Resource Access Manager (AWS RAM) to control which accounts and VPCs can establish communication through a service network.&lt;/p&gt;

&lt;p&gt;What's more, VPC Lattice is designed to be non-invasive and work alongside existing architecture patterns, allowing development teams across your organization to onboard their services incrementally.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;VPC Lattice introduces four key components: Service, Service Directory, Service Network, and Auth Policy. These components simplify how users enable connectivity and apply standard policies to a collection of services. Service networks can be shared across accounts with AWS RAM and associated with VPCs to allow connectivity to a group of services.&lt;/p&gt;

&lt;p&gt;Here is the diagram that illustrates the use of Amazon VPC Lattice and the Service Network Manager to create a service network, define policies, and share cross-account access. &lt;/p&gt;

&lt;p&gt;The Service Network Manager subset at the top consists of four icons representing the process flow:&lt;/p&gt;

&lt;p&gt;1️⃣ The first step involves creating a service network by choosing a name and authentication type. &lt;/p&gt;

&lt;p&gt;2️⃣ The second step consists in defining access and monitoring by setting and managing access policies and selecting log destinations. &lt;/p&gt;

&lt;p&gt;3️⃣ The third step involves associating clients and services, allowing resources in associated VPCs to access the benefits associated with the service network. &lt;/p&gt;

&lt;p&gt;4️⃣The fourth step consists in adding specific assistance or service networks to AWS RAM shares to facilitate cross-account access.&lt;/p&gt;

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

&lt;p&gt;The Service Owner subset at the bottom consists of three steps:&lt;/p&gt;

&lt;p&gt;1️⃣ The first step involves creating a service by identifying the benefit and defining access and monitoring. &lt;/p&gt;

&lt;p&gt;2️⃣ The second step consists in defining routing by adding listeners and rules that point to the target groups that store the service. &lt;/p&gt;

&lt;p&gt;3️⃣ The third step consists in selecting the networks from the service that receives traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;There are three factors that compose the price of VPC Lattice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;number of provisioned services&lt;/li&gt;
&lt;li&gt;traffic volume to and from each service&lt;/li&gt;
&lt;li&gt;number of requests each service receives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the detailed and actual price list on the services' pricing page — &lt;a href="https://aws.amazon.com/vpc/lattice/pricing/" rel="noopener noreferrer"&gt;&lt;strong&gt;link&lt;/strong&gt;&lt;/a&gt;, but here is an example: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You provision a service network in the US East (N. Virginia) Region and associate &lt;strong&gt;100 services&lt;/strong&gt; to it. In a month, &lt;strong&gt;each service processes 100 GB of data at 200,000 requests per hour&lt;/strong&gt;. In this example, we calculate your charges as follows (all prices shown in USD):&lt;/p&gt;

&lt;p&gt;Monthly hourly charges:&lt;br&gt;
You pay &lt;strong&gt;$0.025 per hour for each service&lt;/strong&gt; in US East (N. Virginia)&lt;br&gt;
We assume that a month equals 730 hours (8,760 hours in a year/12 months = 730 hours per month)&lt;br&gt;
100 services * $0.025 per hour * 730 hours = &lt;strong&gt;$1,825.00 per month&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Win-win for Ops and Developers
&lt;/h2&gt;

&lt;p&gt;Overall, VPC Lattice bridges the gap between developers and cloud administrators by providing role-specific features and capabilities. Developers can focus on building applications, not networks, while cloud and network administrators can increase their organization's security posture by enabling authentication, authorization, and encryption consistently across mixed computing environments.&lt;/p&gt;

&lt;p&gt;Currently, Amazon VPC Lattice is in Preview in the US West (Oregon) region. I'm excited to see how VPC Lattice will shape the future of networking and make it even easier for developers to build complex applications. 🚀&lt;/p&gt;

&lt;p&gt;Some additional resources to learn more about Lattice:&lt;/p&gt;

&lt;p&gt;Presentation at re:Invent 2022 &lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/fRjD1JI0H5w"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A blog post at AWS with examples &lt;a href="https://aws.amazon.com/blogs/aws/introducing-vpc-lattice-simplify-networking-for-service-to-service-communication-preview/" rel="noopener noreferrer"&gt;Introducing VPC Lattice – Simplify Networking for Service-to-Service Communication&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devrel</category>
      <category>cloud</category>
      <category>news</category>
    </item>
    <item>
      <title>Five Practical Ways To Get The Verified EC2 AMI</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Tue, 26 Jul 2022 15:53:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/five-practical-ways-to-get-the-verified-ec2-ami-4im7</link>
      <guid>https://dev.to/aws-builders/five-practical-ways-to-get-the-verified-ec2-ami-4im7</guid>
      <description>&lt;p&gt;EC2 AMI catalog consists of more than 160k public AMIs — a mix of Images created by users, published by vendors, and provided by AWS.&lt;/p&gt;

&lt;p&gt;So how to ensure that an AMI comes from the verified vendor or that is an official AMI published by AWS?&lt;/p&gt;

&lt;p&gt;How to find the trusted AMI among them all when you’re about to launch an EC2 Instance?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnoarulewa2u1ouokw31z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnoarulewa2u1ouokw31z.png" alt="Find the ~~Waldo~~ verified AMI owner" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On AWS, it’s typical that something can be made or done in several ways — that’s awesome. Some of them work better than others, some methods are official, and some you can use just for fun (&lt;a href="https://www.lastweekinaws.com/blog/the-17-ways-to-run-containers-on-aws/" rel="noopener noreferrer"&gt;check&lt;/a&gt; &lt;a href="https://www.lastweekinaws.com/blog/17-more-ways-to-run-containers-on-aws/" rel="noopener noreferrer"&gt;that&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In this article, I will describe five ways of getting the official and verified AMI for your next EC2 Instance launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use EC2 Launch Wizard and AMI Catalog to get the official AMI
&lt;/h2&gt;

&lt;p&gt;When launching an EC2 Instance from a Management Console, you can apply the “Verified Provider” filter for the Community AMIs tab to ensure you get an AMI from a verified provider. &lt;/p&gt;

&lt;p&gt;The “Verified provider” label means an AMI is owned by an Amazon verified account.&lt;/p&gt;

&lt;p&gt;In the following example, I want to make sure that the Ubuntu 20.04 AMI comes from the verified source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg9z5xv41gpdqd5q1coh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg9z5xv41gpdqd5q1coh.png" alt="Verified AMI in the AMI Catalog" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the past, you had to compare the AMI Owner ID with the publicly shared list of verified Owner IDs for every region. &lt;/p&gt;

&lt;p&gt;Not rocket science, but it takes time. So now it’s much more straightforward, thanks to the “Verified Provider” label.&lt;/p&gt;

&lt;p&gt;This feature also works great when you are creating a Launch Template. For example, if you want to create a fleet of &lt;a href="https://devdosvid.blog/2021/10/24/auto-scaling-group-for-your-macos-ec2-instances-fleet/" rel="noopener noreferrer"&gt;macOS Instances with AutoScaling&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The Launch Template creation wizard seamlessly guides you from itself to the AMI Catalog (where you can search and pick the AMI) and back again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look for verified AMIs on the AMI page
&lt;/h2&gt;

&lt;p&gt;Another interface in the Management Console acts as the AMI browser. It does not have any fancy name except for the “AMIs page”, but you probably already know about it: it looks like a list of AMIs, and you can see it when you click on the “AMIs” menu item on the left side of the EC2 page menu.&lt;/p&gt;

&lt;p&gt;The AMI page allows you to leverage the API filters to narrow down the search, and the “Owner alias” filter is the one you need to ensure that an AMI comes from a trusted owner.&lt;/p&gt;

&lt;p&gt;Here is how it looks for my search of the official Amazon Linux 2 AMI:&lt;/p&gt;

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

&lt;p&gt;AMIs shared by verified sources have &lt;code&gt;amazon&lt;/code&gt; (for AWS) or &lt;code&gt;aws-marketplace&lt;/code&gt; (for AWS partners) as the value for the Owner alias filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the EC2 AMI with Terraform
&lt;/h2&gt;

&lt;p&gt;Finding the official AMI with Terraform is also simple — the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami" rel="noopener noreferrer"&gt;aws_ami data source&lt;/a&gt; does the job.&lt;/p&gt;

&lt;p&gt;For example, here is how you can find the same Amazon Linux 2 AMI by specifying the &lt;code&gt;amazon&lt;/code&gt; as the value for the &lt;code&gt;owner&lt;/code&gt; argument of the data source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fqr7donl6pmgfz4w3zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fqr7donl6pmgfz4w3zg.png" alt="Finding the official Amazon Linux 2 AMI with Terraform" width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compare that with the filters on the AMI page — it looks similar, right? This is because of how Terraform works: it translates your code into API calls and sends them to AWS API endpoints.&lt;/p&gt;

&lt;p&gt;If you’re very new to Terraform, I suggest reading this article to understand the basic concepts: &lt;a href="https://devdosvid.blog/2020/05/02/terraform-explained-in-english/" rel="noopener noreferrer"&gt;Terraform explained in English&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the official AWS AMI using Describe Images CLI
&lt;/h2&gt;

&lt;p&gt;Sometimes you might need to get the AMI from CLI to pass it along as an argument downstream of the pipeline.&lt;/p&gt;

&lt;p&gt;This can be done with the &lt;strong&gt;ec2 describe-images&lt;/strong&gt; command of the AWS CLI&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cxxcld8wpikp1hshlnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cxxcld8wpikp1hshlnx.png" alt="Find the verified AMI with AWS CLI" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The API filters I mentioned before also work here — use them to narrow your search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the trusted AWS AMI with SSM
&lt;/h2&gt;

&lt;p&gt;Another way that involves AWS CLI is the &lt;strong&gt;ssm get-parameter&lt;/strong&gt; command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwbo42vj41hs90rq2v62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwbo42vj41hs90rq2v62.png" alt="Get the latest EKS optimized AMI from SSM" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It reveals one helpful feature of the Systems Manager — the Public parameters.&lt;/p&gt;

&lt;p&gt;Systems Manager Public parameters are how AWS distributes some widely used artifacts related to their services.&lt;/p&gt;

&lt;p&gt;For example, you can find official AMIs for many distributives there: Amazon Linux, Windows, macOS, Bottlerocket, Ubuntu, Debian, and FreeBSD.&lt;/p&gt;

&lt;p&gt;Read more at the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-finding-public-parameters.html" rel="noopener noreferrer"&gt;Finding public parameters&lt;/a&gt; documentation page if you want to know more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are all verified AMIs good?
&lt;/h2&gt;

&lt;p&gt;The “Verified provider” badge can be earned by a third party only when an AMI developer is registered as a Seller on the AWS Marketplace.&lt;/p&gt;

&lt;p&gt;Becoming a Seller there is not trivial and requires some &lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/user-guide-for-sellers.html#seller-requirements-for-publishing-free-products" rel="noopener noreferrer"&gt;conditions&lt;/a&gt; to be met, and the &lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/seller-registration-process.html" rel="noopener noreferrer"&gt;registration process&lt;/a&gt; itself also implies submitting the tax and banking information.&lt;/p&gt;

&lt;p&gt;Additionally, there are &lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/product-and-ami-policies.html" rel="noopener noreferrer"&gt;specific policies and review processes&lt;/a&gt; apply to all AMIs submitted to the Marketplace.&lt;/p&gt;

&lt;p&gt;So it is okay to trust the third-party vendors with the “Verified” badge on a certain level. However, it is also always good to have additional scans and validation of the software you use. 🪲 😉&lt;/p&gt;





&lt;div class="ltag__user ltag__user__id__51518"&gt;
    &lt;a href="/svasylenko" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg" alt="svasylenko image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/svasylenko"&gt;Serhii Vasylenko&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/svasylenko"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>devops</category>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>New Lifecycle Options and Refactoring Capabilities in Terraform 1.1 and 1.2</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Thu, 05 May 2022 13:14:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/new-lifecycle-options-and-refactoring-capabilities-in-terraform-11-and-12-j19</link>
      <guid>https://dev.to/aws-builders/new-lifecycle-options-and-refactoring-capabilities-in-terraform-11-and-12-j19</guid>
      <description>&lt;p&gt;In this blog, I would like to tell you about new cool features that Terraform 1.1 and 1.2 bring. It feels like Terraform has doubled its speed of delivering the new features after they released the 1.0. 🤩&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All code examples are based on the AWS Terraform provider ⛅️💛&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s been only a few months since Terraform 1.1 was released with the &lt;code&gt;moved&lt;/code&gt; block that empowers the code refactoring.&lt;/p&gt;

&lt;p&gt;Now Terraform 1.2 is almost &lt;a href="*https://github.com/hashicorp/terraform/releases/tag/v1.2.0-rc1*"&gt;ready&lt;/a&gt; (as I am writing this blog in early May 2022) to bring three new efficient controls to the resource lifecycle.&lt;br&gt;&lt;br&gt;
These are three new expressions: &lt;code&gt;precondition&lt;/code&gt;, &lt;code&gt;postcondition&lt;/code&gt;, and &lt;code&gt;replace_triggered_by&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Terraform Code Refactoring With the Moved Block
&lt;/h2&gt;

&lt;p&gt;Starting from the 1.1 version, Terraform users can use the &lt;code&gt;moved&lt;/code&gt; block to describe the changes in resource or module addresses (or resources inside a module) in the form of code.&lt;br&gt;&lt;br&gt;
Once that is described, Terraform performs the movement of the resource within the state during the first apply.&lt;/p&gt;

&lt;p&gt;In other words, what this feature gives you, is the ability to document your &lt;code&gt;terraform state mv&lt;/code&gt; actions, so you and other project or module users don’t need to perform them manually.&lt;/p&gt;

&lt;p&gt;As your code evolves, a resource or module can have several &lt;code&gt;moved&lt;/code&gt; blocks associated with it, and Terraform will thoroughly reproduce the whole history of its movement within a state (i.e., renaming).&lt;/p&gt;

&lt;p&gt;Let’s review some examples that illustrate how it works.&lt;/p&gt;
&lt;h3&gt;
  
  
  Moving a resource
&lt;/h3&gt;

&lt;p&gt;In a module, I have a bucket policy that has a generic, meaningless name. It is used in a module that creates a CloudFront distribution with an S3 bucket.&lt;/p&gt;

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

&lt;p&gt;It’s pretty OK to name a resource like that if you have only a single instance of that kind in your code.&lt;/p&gt;

&lt;p&gt;Later, when I need to add another policy to the module, I don’t want to name it “that”. Instead, I want my policies to have meaningful names now.&lt;br&gt;&lt;br&gt;
For example, I could rename the old policy with the &lt;code&gt;terraform state mv&lt;/code&gt; command, but other users of my module would not know about that.&lt;/p&gt;

&lt;p&gt;That is where the &lt;code&gt;moved&lt;/code&gt; block turns out to be helpful: I can document the name change, and later, everyone else who uses my module will get the same renaming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4c3f9wvdtqwwy0cdcn00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4c3f9wvdtqwwy0cdcn00.png" alt="Resource address update with the Moved block" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform follows the instructions inside the &lt;code&gt;module&lt;/code&gt; block to plan and apply changes. Although the resource address update is not counted as a change in the Plan output, Terraform will perform that update during apply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb38nfp4wp00xqb3ns374.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb38nfp4wp00xqb3ns374.png" alt="Terraform plan output" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Moving a module
&lt;/h3&gt;

&lt;p&gt;The same approach can be applied to a module.&lt;/p&gt;

&lt;p&gt;Here, I use two modules to create static hosting for a website with a custom TLS certificate: a combo of the CloudFront + S3 + ACM services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uz7lbyvp31meqfuaxgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uz7lbyvp31meqfuaxgq.png" alt="Two modules with generic names" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, if I need to add another couple of the CDN+Certificate modules, I would like to have meaningful names in my code so clearly distinguish one from another.&lt;/p&gt;

&lt;p&gt;Therefore, I would add two &lt;code&gt;moved&lt;/code&gt; blocks — one per module call.&lt;/p&gt;

&lt;p&gt;And by the way, since I renamed the module (from &lt;code&gt;cert&lt;/code&gt; to &lt;code&gt;example_com_cert&lt;/code&gt;), I need to update all references to that module’s outputs in the code too.&lt;/p&gt;

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

&lt;p&gt;However, there is one nuance: when you rename a module and declare that in the &lt;code&gt;moved&lt;/code&gt; block, you need to run the &lt;code&gt;terraform init&lt;/code&gt; before applying the change because Terraform must initialize the module with the new name first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funp7ccm3nxpjesu1zt6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funp7ccm3nxpjesu1zt6c.png" alt="Terraform error: module not installed" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are some more advanced actions you can make with the &lt;code&gt;moved&lt;/code&gt; block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement count and for_each meta-arguments to resources and modules&lt;/li&gt;
&lt;li&gt;Break one module into multiple Check the following detailed guide from HashiCorp that explains how to do that — &lt;a href="https://www.terraform.io/language/modules/develop/refactoring" rel="noopener noreferrer"&gt;Refactoring&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Introducing the &lt;code&gt;moved&lt;/code&gt; blocks into your codebase defacto starts the refactoring process for your module users. But the finale of that refactoring happens when you ultimately remove these blocks.&lt;/p&gt;

&lt;p&gt;Therefore, here is some advice on how to manage that:&lt;/p&gt;

&lt;p&gt;💡 Keep the &lt;code&gt;moved&lt;/code&gt; blocks in your code for long. For example, when removing a &lt;code&gt;moved&lt;/code&gt; block from the code, Terraform does not treat the new object name as a renaming anymore. Instead, Terraform will plan to delete the resource or module with the old name instead of renaming it.&lt;br&gt;
💡 Keep the complete chains of object renaming (sequence of moves). The whole history of object movement ensures that users with different module versions will get a consistent and predictable behavior of the refactoring.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lifecycle expressions: precondition, postcondition, and replace_triggered_by
&lt;/h2&gt;

&lt;p&gt;Terraform 1.2 fundamentally improves the &lt;code&gt;lifecycle&lt;/code&gt; meta-argument by adding three new configuration options with rich capabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm4sqdi9d1qecilksdmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm4sqdi9d1qecilksdmu.png" alt="New configuration options for the lifecycle meta-argument" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Precondition and Postcondition
&lt;/h3&gt;

&lt;p&gt;When you need to make sure that specific condition is met before or after you create a resource, you can use &lt;code&gt;postcondition&lt;/code&gt; and &lt;code&gt;precondition&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;condition&lt;/em&gt; here — is some data or information about a resource you need to confirm to apply the code.&lt;/p&gt;

&lt;p&gt;Here are a few examples of such conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate some attributes of the Data Source that you cannot check using filters or other available arguments;&lt;/li&gt;
&lt;li&gt;Confirm the Resource argument that can compound several variables (e.g., list);&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Precondition&lt;/strong&gt; works as an expectation or a guess about some external (but within a module) value that a resource depends on.&lt;br&gt;
💡 &lt;strong&gt;Postcondition&lt;/strong&gt; works as the assurance that a resource fulfils a specific condition so other resources may rely on that. If postcondition fails for a resource, this prevents changes to all other resources that depend on it.&lt;/p&gt;

&lt;p&gt;Let’s review this new feature with an example of &lt;code&gt;postcondition&lt;/code&gt; usage.&lt;/p&gt;

&lt;p&gt;Consider the following case: our module receives AMI ID as the input variable, and that AMI should be used in the Launch Template then; we also have the requirement for the EC2 instance created from that Launch Template — its root EBS size must be equal or bigger than 600 GB.&lt;/p&gt;

&lt;p&gt;We cannot validate the EBS size using the variable that accepts the AMI ID. But we can write a &lt;strong&gt;postcondition&lt;/strong&gt; for the Data Source that gets the information about the AMI and reference that Data Source in the Launch Template resource afterward.&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;condition&lt;/code&gt; argument within the block accepts any of Terraform’s built-in functions or language operators.&lt;/p&gt;

&lt;p&gt;The special &lt;code&gt;self&lt;/code&gt; object is available only for the &lt;code&gt;postcondition&lt;/code&gt; block because it assumes that validation can be performed after the object is created and its attributes are known.&lt;/p&gt;

&lt;p&gt;Later, if a module user specifies the AMI with an EBS size lesser than 600 GB, Terraform will fail to create the Launch Template because it depends on the Data Source that did not pass the postcondition check.&lt;/p&gt;

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

&lt;p&gt;Terraform tries to evaluate the condition expressions as soonest: sometimes Terraform can check the value during the planning phase, but sometimes that can be done only after the resource is created if the value is unknown.&lt;/p&gt;
&lt;h3&gt;
  
  
  Validating module output with precondition
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;precondition&lt;/code&gt; block is also available for the module outputs.&lt;/p&gt;

&lt;p&gt;Just like the variable validation block assures that module input meets certain expectations, the &lt;code&gt;precondition&lt;/code&gt; is intended to ensure that a module produces the valid output.&lt;/p&gt;

&lt;p&gt;Here is an example: a module that creates an ACM certificate must prevent the usage of a specific domain name in the certificate’s Common Name or its SANs.&lt;/p&gt;

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

&lt;p&gt;In this case, instead of validating several input variables, we can write the validation only once for the output.&lt;/p&gt;
&lt;h3&gt;
  
  
  Trigger resource replacement with replace_triggered_by
&lt;/h3&gt;

&lt;p&gt;Sometimes it’s needed to specify the dependency in the way that recreates a resource when another resource or its attribute changes.&lt;/p&gt;

&lt;p&gt;This is useful when two (or more) resources do not have any explicit dependency.&lt;/p&gt;

&lt;p&gt;Consider the following case: you have two EC2 instances, A and B, and need to recreate the B instance if the private IP of instance A is changed.&lt;/p&gt;

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

&lt;p&gt;This is extremely useful when you’re dealing with logical abstractions over the set of resources.&lt;/p&gt;

&lt;p&gt;Replacement is triggered when:&lt;/p&gt;

&lt;p&gt;💡 Any of the resources referenced in &lt;code&gt;replace_triggered_by&lt;/code&gt; are updated&lt;br&gt;
💡 Any value is set to the resource attribute that is referenced in &lt;code&gt;replace_triggered_by&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started with Terraform 1.1 and 1.2
&lt;/h2&gt;

&lt;p&gt;If you’re still using older Terraform versions, these new features might be a good motivation for you to upgrade!&lt;/p&gt;

&lt;p&gt;Before upgrading, be sure to read the upgrade notes for the specific version at the &lt;a href="https://github.com/hashicorp/terraform/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, an excellent tool can help with fast switching between different Terraform versions while you’re experimenting — &lt;a href="https://tfswitch.warrensbox.com/" rel="noopener noreferrer"&gt;tfswitch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you liked that article, you might also like the others wrote about Terraform: &lt;strong&gt;&lt;a href="https://devdosvid.blog/series/terraform-proficiency/" rel="noopener noreferrer"&gt;Terraform Proficiency&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;div class="ltag__user ltag__user__id__51518"&gt;
    &lt;a href="/svasylenko" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg" alt="svasylenko image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/svasylenko"&gt;Serhii Vasylenko&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/svasylenko"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>terraform</category>
      <category>programming</category>
      <category>news</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Some Techniques to Enhance Your Terraform Proficiency</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Tue, 18 Jan 2022 09:05:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/some-techniques-to-enhance-your-terraform-proficiency-4n9e</link>
      <guid>https://dev.to/aws-builders/some-techniques-to-enhance-your-terraform-proficiency-4n9e</guid>
      <description>&lt;p&gt;Terraform built-in functionality is very feature-rich: functions, expressions, and meta-arguments provide many ways to shape the code and fit it to a particular use case. &lt;/p&gt;

&lt;p&gt;I want to share a few valuable practices to boost your Terraform expertise in this blog.&lt;/p&gt;

&lt;p&gt;Two notes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1️⃣ &lt;em&gt;Some code examples in this article will work with Terraform version 0.15 and onwards. But if you’re still using 0.14 or lower, here’s another motivation for you to upgrade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;2️⃣ &lt;em&gt;I'll be using AWS Terraform provider code examples throughout the article but mentioned Terraform functions and expressions are provider-agnostic and will work the same with other providers&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conditional resources creation
&lt;/h2&gt;

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

&lt;p&gt;Let’s start from the most popular one (although, still may be new for somebody): whether to create a resource depending on some fact, e.g., the value of a variable. Terraform meta-argument &lt;code&gt;count&lt;/code&gt; helps to describe that kind of logic.&lt;/p&gt;

&lt;p&gt;Here is how it may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssm_parameter"&lt;/span&gt; &lt;span class="s2"&gt;"ami_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami_channel&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami_channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami_channel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The notation &lt;code&gt;var.ami_channel == "" ? 0 : 1&lt;/code&gt; is called &lt;em&gt;conditional expression&lt;/em&gt; and means the following: if my variable is empty (&lt;code&gt;var.ami_channel == ""&lt;/code&gt; — hence, true) then set the count to 0, otherwise set to 1.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;true_val&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;false_val&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this illustration, I want to get the AMI ID from the SSM Parameter only if the AMI channel (e.g., beta or alpha) is specified. Otherwise, providing that the &lt;code&gt;ami_channel&lt;/code&gt; variable is an empty string by default (""), the data source should not be created.&lt;/p&gt;

&lt;p&gt;When following this method, keep in mind that the resource address will contain the index identifier. So when I need to use the value of the SSM parameter from our example, I need to reference it the following way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;ami_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssm_parameter&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;count&lt;/code&gt; meta-argument can also be used to create a whole module conditionally:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create_bucket&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/s3_bucket"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-unique-bucket"&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;var.create_bucket == true ? 1 : 0&lt;/code&gt; expression can be written even shorter: &lt;code&gt;var.create_bucket ? 1 : 0&lt;/code&gt; because the &lt;code&gt;create_bucket&lt;/code&gt; variable has boolean type, apparently.&lt;/p&gt;

&lt;p&gt;But what if you need to produce more than one instance of a resource or module? And still be able to avoid their creation.&lt;/p&gt;

&lt;p&gt;Another meta-argument — &lt;code&gt;for_each&lt;/code&gt; — will do the trick.&lt;/p&gt;

&lt;p&gt;For example, this is how it looks for a module:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_names&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_names&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/s3_bucket"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;enable_encryption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this illustration, I also used a conditional expression that makes Terraform iterate through the set of values of &lt;code&gt;var.bucket_names&lt;/code&gt; if it’s not empty and create several modules. Otherwise, do not iterate at all and do not create anything.&lt;/p&gt;

&lt;p&gt;The same can be done for the resources. For example, when you need to create an arbitrary number of security group rules, e.g., to allowlist some IPs for your bastion host:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group_rule"&lt;/span&gt; &lt;span class="s2"&gt;"allowlist"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bastion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And just like with the &lt;code&gt;count&lt;/code&gt; meta-argument, with the &lt;code&gt;for_each&lt;/code&gt;, resource addresses will have the identifier named by the values provided to &lt;code&gt;for_each&lt;/code&gt;. For example, here is how I would reference a resource created in the module with &lt;code&gt;for_each&lt;/code&gt; described earlier:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"photos"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Conditional resource arguments (attributes) setting
&lt;/h2&gt;

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

&lt;p&gt;Now let’s go deeper and see how resource arguments can be conditionally set (or not).&lt;/p&gt;

&lt;p&gt;First, let’s review the conditional argument value setting with the &lt;code&gt;null&lt;/code&gt; data type:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_launch_template"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-launch-template"&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_default_keypair&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keypair_name&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here I want to skip the usage of the EC2 Key Pair for the Launch Template in some instances and Terraform allows me to write the conditional expression that will set the &lt;code&gt;null&lt;/code&gt; value for the argument. It means the &lt;em&gt;absence&lt;/em&gt; or &lt;em&gt;omission&lt;/em&gt; and Terraform would behave the same as if you did not specify the argument at all.&lt;/p&gt;

&lt;p&gt;Dynamic blocks are another case where this technique suits best. Take a look at the following piece of CloudFront resource code where I want to either describe the configuration for the custom error response or omit that completely:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"cdn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"custom_error_response"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_error_response&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_error_response&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;iterator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cer&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;error_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;error_caching_min_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"error_caching_min_ttl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;response_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"response_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;response_page_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"response_page_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;custom_error_response&lt;/code&gt; variable is &lt;code&gt;null&lt;/code&gt; by default, but it has the &lt;code&gt;object&lt;/code&gt; type, and users can assign the variable with the required nested specifications if needed. And when they do it, Terraform will add the &lt;code&gt;custom_error_response&lt;/code&gt; block to the resource configuration. Otherwise, it will be omitted entirely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Convert types with ease
&lt;/h2&gt;

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

&lt;p&gt;Ok, let’s move to the less conditional things now 😅&lt;/p&gt;

&lt;p&gt;Terraform has several type conversion functions: &lt;code&gt;tobool()&lt;/code&gt;, &lt;code&gt;tolist()&lt;/code&gt;,&lt;code&gt;tomap()&lt;/code&gt;, &lt;code&gt;tonumber()&lt;/code&gt;, &lt;code&gt;toset()&lt;/code&gt;, and &lt;code&gt;tostring()&lt;/code&gt;. Their purpose is to convert the input values to the compatible types.&lt;/p&gt;

&lt;p&gt;For example, suppose I need to pass the set to the &lt;code&gt;for_each&lt;/code&gt; (it accepts only sets and maps types of value), but I got the list as an input; let’s say I got it as an output from another module. In such a case, I would do something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remote_access_ports&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However, I can make my code cleaner and avoid the explicit conversion — I just need to define the value type in the configuration block of the &lt;code&gt;my_list&lt;/code&gt; variable. Terraform will do the conversion automatically when the value is assigned.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"remote_access_ports"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Ports for remote access"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;While Terraform can do a lot of implicit conversions for you, explicit type conversions are practical during values normalization or when you need to calculate some complex value for a variable. For example, the Local Values, known as &lt;code&gt;locals&lt;/code&gt;, are the most suitable place for doing that.&lt;/p&gt;

&lt;p&gt;By the way, although there is a &lt;code&gt;tolist()&lt;/code&gt; function, there is no such thing as the &lt;code&gt;tostring()&lt;/code&gt; function. But what if you need to convert the list to string in Terraform?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;one()&lt;/code&gt; function can help here: it takes a list, set, or tuple value with either zero or one element and returns either &lt;code&gt;null&lt;/code&gt; or that one element in the form of string.&lt;/p&gt;

&lt;p&gt;It’s useful in cases when a resource created using conditional expression is represented as either a zero- or one-element list, and you need to get a single value which may be either &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_kms_key"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ebs_encrypted&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;enable_key_rotation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_kms_alias"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ebs_encrypted&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alias/encrypt-ebs"&lt;/span&gt;
  &lt;span class="nx"&gt;target_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="nx"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Write YAML or JSON as Terraform code (HCL)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdwhx72j67nywt1wqcfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdwhx72j67nywt1wqcfo.png" alt="Write YAML or JSON as Terraform code" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you need to supply JSON or YAML files to the services you manage with Terraform. For example, if you want to create something with CloudFormation using Terraform (and I am not kidding). Sometimes the AWS Terraform provider does not support the needed resource, and you want to maintain the whole infrastructure code using only one tool.&lt;/p&gt;

&lt;p&gt;Instead of maintaining another file in JSON or YAML format, you can embed JSON or YAML code management into HCL by taking benefit of the &lt;code&gt;jsonencode()&lt;/code&gt; or &lt;code&gt;yamlencode()&lt;/code&gt; functions.&lt;/p&gt;

&lt;p&gt;The attractiveness of this approach is that you can reference other Terraform resources or their attributes right in the code of your object, and you have more freedom in terms of the code syntax and its formatting comparable to native JSON or YAML.&lt;/p&gt;

&lt;p&gt;Here is how it looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;some_string&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ult"&lt;/span&gt;
  &lt;span class="nx"&gt;myjson_object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"Hashicorp Products"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Terra&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;
      &lt;span class="nx"&gt;Con&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"sul"&lt;/span&gt;
      &lt;span class="nx"&gt;Vag&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"rant"&lt;/span&gt;
      &lt;span class="nx"&gt;Va&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some_string&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The value of the &lt;code&gt;myjson_object&lt;/code&gt; local variable would look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Hashicorp Products"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Con"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Terra"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Va"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ult"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Vag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rant"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And here is a piece of real-world example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cf_template_body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Resources&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;DedicatedHostGroup&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Type&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::ResourceGroups::Group"&lt;/span&gt;
        &lt;span class="nx"&gt;Properties&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt;
          &lt;span class="nx"&gt;Configuration&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;Type&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"AWS::EC2::HostManagement"&lt;/span&gt;
              &lt;span class="nx"&gt;Parameters&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"auto-allocate-host"&lt;/span&gt;
                  &lt;span class="nx"&gt;Values&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auto_allocate_host&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Templatize stuff
&lt;/h2&gt;

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

&lt;p&gt;The last case in this blog but not the least by its efficacy — render source file content as a template in Terraform.&lt;/p&gt;

&lt;p&gt;Let’s review the following scenario: you launch an EC2 instance and want to supply it with a bash script (via the user-data parameter) for some additional configuration at launch.&lt;/p&gt;

&lt;p&gt;Suppose we have the following bash script &lt;code&gt;instance-init.sh&lt;/code&gt; that sets the hostname and registers our instance in a monitoring system:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;hostname &lt;/span&gt;example.com
bash /opt/system-init/register-monitoring.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But what if you want to set a different hostname per instance, and some instances should not be registered in the monitoring system?&lt;/p&gt;

&lt;p&gt;In such a case, here is how the script file content will look:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

hostname ${system_hostname}
%{ if register_monitoring }
bash /opt/system-init/register-monitoring.sh
%{endif}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And when you supply this file as an argument for the EC2 instance resource in Terraform, you will use the &lt;code&gt;templatefile()&lt;/code&gt; function to make the magic happen:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_ami_id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/instance-init.tftpl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;system_hostname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;system_hostname&lt;/span&gt;
    &lt;span class="nx"&gt;register_monitoring&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_to_monitoring&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And of course, you can create a template from any file type. The only requirement here is that the template file must exist on the disk at the beginning of the Terraform execution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Terraform is far beyond the standard resource management operations. With the power of built-in functions, you can write more versatile code and reusable Terraform modules.&lt;/p&gt;

&lt;p&gt;✅ Use &lt;a href="https://www.terraform.io/language/expressions/conditionals" rel="noopener noreferrer"&gt;conditional expressions&lt;/a&gt; with &lt;a href="https://www.terraform.io/language/meta-arguments/count" rel="noopener noreferrer"&gt;count&lt;/a&gt; and &lt;a href="https://www.terraform.io/language/meta-arguments/for_each" rel="noopener noreferrer"&gt;for_each&lt;/a&gt; meta-arguments, when the creation of a resource depends on some context or user inout.&lt;/p&gt;

&lt;p&gt;✅ Take advantage of &lt;a href="https://www.terraform.io/language/expressions/types#type-conversion" rel="noopener noreferrer"&gt;implicit types conversion&lt;/a&gt; when working with input variables and their values to keep your code cleaner.&lt;/p&gt;

&lt;p&gt;✅ Embed YAML and JSON-based objects right into your Terraform code using built-in &lt;a href="https://www.terraform.io/language/functions/jsonencode" rel="noopener noreferrer"&gt;encoding&lt;/a&gt; &lt;a href="https://www.terraform.io/language/functions/yamlencode" rel="noopener noreferrer"&gt;functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;✅ And when you need to pass some files to the managed service, you can treat them as &lt;a href="https://www.terraform.io/language/functions/templatefile" rel="noopener noreferrer"&gt;templates&lt;/a&gt; and make them multipurpose.&lt;/p&gt;

&lt;p&gt;Thank you for reading down to this point! 🤗&lt;/p&gt;

&lt;p&gt;And if you have some favorite Terraform tricks — I would love to know!&lt;/p&gt;




&lt;div class="ltag__user ltag__user__id__51518"&gt;
    &lt;a href="/svasylenko" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg" alt="svasylenko image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/svasylenko"&gt;Serhii Vasylenko&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/svasylenko"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Apply Cloudfront Security Headers Policy With Terraform</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Fri, 05 Nov 2021 13:23:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/apply-cloudfront-security-headers-policy-with-terraform-fd3</link>
      <guid>https://dev.to/aws-builders/apply-cloudfront-security-headers-policy-with-terraform-fd3</guid>
      <description>&lt;p&gt;In November 2021, AWS announced Response Headers Policies — native support of response headers in CloudFront. You can read the full announcement here: &lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-response-headers-policies/" rel="noopener noreferrer"&gt;Amazon CloudFront introduces Response Headers Policies&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I said “native” because previously you could set response headers either using &lt;a href="https://serhii.vasylenko.info/2021/05/21/configure-http-security-headers-with-cloudfront-functions.html" rel="noopener noreferrer"&gt;CloudFront Functions&lt;/a&gt; or &lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/" rel="noopener noreferrer"&gt;Lambda@Edge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And one of the common use cases for that was to set security headers. So now you don’t need to add intermediate requests processing to modify the headers: CloudFront does that for you &lt;strong&gt;with no additional fee&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage Security Headers as Code
&lt;/h2&gt;

&lt;p&gt;Starting from the &lt;a href="https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#3640-november-04-2021" rel="noopener noreferrer"&gt;3.64.0&lt;/a&gt; version of Terraform AWS provider, you can create the security headers policies and apply them for your distribution.&lt;/p&gt;

&lt;p&gt;Let’s see how that looks!&lt;/p&gt;

&lt;p&gt;First, you need to describe the &lt;code&gt;aws_cloudfront_response_headers_policy&lt;/code&gt; resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_response_headers_policy"&lt;/span&gt; &lt;span class="s2"&gt;"security_headers_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-security-headers-policy"&lt;/span&gt;
  &lt;span class="nx"&gt;security_headers_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;content_type_options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;frame_options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;frame_option&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DENY"&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;referrer_policy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;referrer_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"same-origin"&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;xss_protection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mode_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;strict_transport_security&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;access_control_max_age_sec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"63072000"&lt;/span&gt;
      &lt;span class="nx"&gt;include_subdomains&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;preload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;content_security_policy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;content_security_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"frame-ancestors 'none'; default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"&lt;/span&gt;
      &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;List of security headers used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#x-content-type-options" rel="noopener noreferrer"&gt;X-Content-Type-Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#x-frame-options" rel="noopener noreferrer"&gt;X-Frame-Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#referrer-policy" rel="noopener noreferrer"&gt;Referrer Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#x-xss-protection" rel="noopener noreferrer"&gt;X-XSS-Protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#http-strict-transport-security" rel="noopener noreferrer"&gt;Strict Transport Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://infosec.mozilla.org/guidelines/web_security#content-security-policy" rel="noopener noreferrer"&gt;Content Security Policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The values for the security headers can be different, of course. However, the provided ones cover the majority of cases. And you can always get the up to date info about these headers and possible values here: &lt;a href="https://infosec.mozilla.org/guidelines/web_security" rel="noopener noreferrer"&gt;Mozilla web Security Guidelines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, you could notice that provided example uses the &lt;code&gt;override&lt;/code&gt; argument a lot. The &lt;code&gt;override&lt;/code&gt; argument tells CloudFront to set these values for specified headers despite the values received from the origin. This way, you can enforce your security headers configuration.&lt;/p&gt;

&lt;p&gt;Once you have the &lt;code&gt;aws_cloudfront_response_headers_policy&lt;/code&gt; resource, you can refer to it in the code of &lt;code&gt;aws_cloudfront_distribution&lt;/code&gt; resource inside cache behavior block (default or ordered). For example, in your &lt;code&gt;default_cache_behavior&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default_cache_behavior&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_origin_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_origin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;allowed_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;cached_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;viewer_protocol_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"redirect-to-https"&lt;/span&gt;

    &lt;span class="c1"&gt;# some arguments skipped from listing for the sake of simplicity&lt;/span&gt;

    &lt;span class="nx"&gt;response_headers_policy_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudfront_response_headers_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;security_headers_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# some arguments skipped from listing for the sake of simplicity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  More to read:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_response_headers_policy" rel="noopener noreferrer"&gt;Terraform Resource: aws_cloudfront_response_headers_policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/creating-response-headers-policies.html" rel="noopener noreferrer"&gt;Creating response headers policies - Amazon CloudFront&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html" rel="noopener noreferrer"&gt;Using the managed response headers policies - Amazon CloudFront&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/understanding-response-headers-policies.html" rel="noopener noreferrer"&gt;Understanding response headers policies - Amazon CloudFront&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="ltag__user ltag__user__id__51518"&gt;
    &lt;a href="/svasylenko" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg" alt="svasylenko image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/svasylenko"&gt;Serhii Vasylenko&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/svasylenko"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Auto Scaling Group for your macOS EC2 Instances fleet</title>
      <dc:creator>Serhii Vasylenko</dc:creator>
      <pubDate>Mon, 25 Oct 2021 08:33:05 +0000</pubDate>
      <link>https://dev.to/aws-builders/auto-scaling-group-for-your-macos-ec2-instances-fleet-57pj</link>
      <guid>https://dev.to/aws-builders/auto-scaling-group-for-your-macos-ec2-instances-fleet-57pj</guid>
      <description>&lt;p&gt;It’s been almost a year since I started using macOS EC2 instances on AWS: there were &lt;a href="https://dev.to/aws-builders/mac1-metal-ec2-instance-user-experience-j08"&gt;ups and downs in service offerings&lt;/a&gt; and a lot of discoveries with &lt;a href="https://dev.to/aws-builders/customizing-mac1-metal-ec2-ami-new-guts-more-glory-48da"&gt;macOS AMI build&lt;/a&gt; automation.&lt;/p&gt;

&lt;p&gt;And I like this small but so helpful update of EC2 service very much: with mac1.metal instances, seamless integration of Apple-oriented CI/CD with other AWS infrastructure could finally happen.&lt;/p&gt;

&lt;p&gt;While management of a single mac1.metal node (or a tiny number of ones) is not a big deal (especially when &lt;a href="https://dev.to/aws-builders/terraforming-mac1-metal-at-aws-2lb3"&gt;Dedicated Host support&lt;/a&gt; was added to Terraform provider), governing the fleet of instances is still complicated. Or it has been complicated until recent days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official / Unofficial Auto Scaling for macOS
&lt;/h2&gt;

&lt;p&gt;With a growing number of instances, the following challenges arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale mac1.metal instances horizontally&lt;/li&gt;
&lt;li&gt;Automatically allocate and release Dedicated Hosts needed for instances&lt;/li&gt;
&lt;li&gt;Automatically replace unhealthy instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have worked with AWS before, you know that Auto Scaling Group service can solve such things.&lt;/p&gt;

&lt;p&gt;However, official documentation (as of October 2021) &lt;a href="https://github.com/awsdocs/amazon-ec2-user-guide/blob/269ac7494dd3aef62ae5d45e8b11f7ea5cadd2bf/doc_source/ec2-mac-instances.md" rel="noopener noreferrer"&gt;states&lt;/a&gt;: “You cannot use Mac instances with Amazon EC2 Auto Scaling”.&lt;/p&gt;

&lt;p&gt;But in fact, you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining services to get real power
&lt;/h2&gt;

&lt;p&gt;So how does all that work?&lt;/p&gt;

&lt;p&gt;Let’s review the diagram that illustrates the interconnection between involved services:&lt;/p&gt;

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

&lt;p&gt;With the help of the Licence Manager service and Launch Templates, you can set up EC2 Auto Scaling Group for mac1.metal and leave the automated instance provisioning to the service.&lt;/p&gt;

&lt;h3&gt;
  
  
  License Configuration
&lt;/h3&gt;

&lt;p&gt;First, you need to create a License Configuration so that the Host resource group can allocate the hots.&lt;/p&gt;

&lt;p&gt;Go to AWS License Manager -&amp;gt; Customer managed licenses -&amp;gt; Create customer-managed license.&lt;/p&gt;

&lt;p&gt;Specify &lt;strong&gt;Sockets&lt;/strong&gt; as the Licence type. You may skip setting the Number of Sockets. However, the actual limit of mac1.metal instances per account is regulated by Service Quota. The default number of mac instances allowed per account is 3. Therefore, consider &lt;a href="https://docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html" rel="noopener noreferrer"&gt;increasing&lt;/a&gt; this to a more significant number.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Host resource group
&lt;/h3&gt;

&lt;p&gt;Second, create the Host resource group: AWS License Manager -&amp;gt; Host resource groups -&amp;gt; Create host resource group.&lt;/p&gt;

&lt;p&gt;When creating the Host resource group, check “&lt;strong&gt;Allocate hosts automatically&lt;/strong&gt;” and “&lt;strong&gt;Release hosts automatically&lt;/strong&gt;” but leave “Recover hosts automatically” unchecked. Dedicated Host does &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-hosts-recovery.html#dedicated-hosts-recovery-instances" rel="noopener noreferrer"&gt;not support host recovery&lt;/a&gt; for mac1.metal.&lt;br&gt;
However, Auto Scaling Group will maintain the desired number of instances if one fails the health check (which assumes the case of host failure as well).&lt;/p&gt;

&lt;p&gt;Also, I recommend specifying “mac1” as an allowed Instance family for the sake of transparent resource management: only this instance type is permitted to allocate hosts in the group.&lt;/p&gt;

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

&lt;p&gt;Optionally, you may specify the license association here (the Host group will pick any compatible license) or select the license you created on step one. &lt;/p&gt;
&lt;h3&gt;
  
  
  Launch Template
&lt;/h3&gt;

&lt;p&gt;Create Launch Template: EC2 -&amp;gt; Launch templates -&amp;gt; Create launch template.&lt;/p&gt;

&lt;p&gt;I will skip the description of all Launch Template parameters (but here is a nice &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;), if you don’t mind, and keep focus only on the items relevant to the current case.&lt;/p&gt;

&lt;p&gt;Specify mac1.metal as the Instance type. Later, in Advanced details: find the &lt;strong&gt;Tenancy&lt;/strong&gt; parameter and set it to “Dedicated host”; for &lt;strong&gt;Target host by&lt;/strong&gt; select “Host resource group”, and once selected the new parameter &lt;strong&gt;Tenancy host resource group&lt;/strong&gt; will appear where you should choose your host group; select your license in &lt;strong&gt;License configurations&lt;/strong&gt; parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr566cqm7l0jzu3tozg57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr566cqm7l0jzu3tozg57.png" alt="Launch Template" width="800" height="741"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Auto Scaling Group
&lt;/h3&gt;

&lt;p&gt;Finally, create the Auto Scaling Group: EC2 -&amp;gt; Auto Scaling groups -&amp;gt; Create Auto Scaling group.&lt;/p&gt;

&lt;p&gt;The vital thing to note here — is the availability of the mac1.metal instance in particular AZ.&lt;/p&gt;

&lt;p&gt;Mac instances are available in us-east-1 and &lt;a href="https://aws.amazon.com/about-aws/whats-new/2021/10/amazon-ec2-mac-instances-additional-regions/" rel="noopener noreferrer"&gt;7 more regions&lt;/a&gt;, but not every Availability Zone in the region supports it. So you must figure out which AZ supports the needed instance type.&lt;/p&gt;

&lt;p&gt;There is no documentation for that, but there is an AWS CLI command that can answer this question: &lt;a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-instance-type-offerings.html" rel="noopener noreferrer"&gt;describe-instance-type-offerings — AWS CLI 2.3.0 Command Reference&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an example for the us-east-1 region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; aws ec2 describe-instance-type-offerings &lt;span class="nt"&gt;--location-type&lt;/span&gt; availability-zone-id &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;instance-type,Values&lt;span class="o"&gt;=&lt;/span&gt;mac1.metal &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="nt"&gt;--output&lt;/span&gt; text

INSTANCETYPEOFFERINGS   mac1.metal  use1-az6    availability-zone-id
INSTANCETYPEOFFERINGS   mac1.metal  use1-az4    availability-zone-id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Keep that nuance in mind when selecting a subnet for the mac1.metal instances.&lt;/p&gt;

&lt;p&gt;When you know the AZ, specify the respective Subnet in the Auto Scaling Group settings, and you're ready to go! &lt;/p&gt;
&lt;h2&gt;
  
  
  Bring Infrastructure as Code here
&lt;/h2&gt;

&lt;p&gt;I suggest describing all that as a code. I prefer Terraform, and its AWS provider supports the needed resources. Except one.&lt;/p&gt;

&lt;p&gt;As of October 2021, resources supported :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/servicequotas_service_quota" rel="noopener noreferrer"&gt;aws_servicequotas_service_quota&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/licensemanager_license_configuration" rel="noopener noreferrer"&gt;aws_licensemanager_license_configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template" rel="noopener noreferrer"&gt;aws_launch_template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group" rel="noopener noreferrer"&gt;aws_autoscaling_group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Host resource group is not yet supported by the provider, unfortunately. However, we can use CloudFormation in Terraform to overcome that: describe the Host resource group as &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack" rel="noopener noreferrer"&gt;aws_cloudformation_stack&lt;/a&gt; Terraform resource using CloudFormation template from a file.&lt;/p&gt;

&lt;p&gt;Here is how it looks like:&lt;br&gt;

  Click here to see the code snippet
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_licensemanager_license_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt;
  &lt;span class="nx"&gt;license_counting_type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Socket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudformation_stack"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="c1"&gt;# the name of CloudFormation stack&lt;/span&gt;
  &lt;span class="nx"&gt;template_body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/resource-group-cf-stack-template.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;GroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="c1"&gt;# the name for the Host group, passed to CloudFormation template&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;on_failure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DELETE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;And the next code snippet explains the CloudFromation template (which is the &lt;code&gt;resource-group-cf-stack-template.json&lt;/code&gt; file in the code snippet above)&lt;/p&gt;

&lt;p&gt;
  Click here to see the code snippet
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"GroupName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The name of Host Group"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resources"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DedicatedHostGroup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS::ResourceGroups::Group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GroupName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Configuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS::ResourceGroups::Generic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowed-resource-types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AWS::EC2::Host"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deletion-protection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"UNLESS_EMPTY"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS::EC2::HostManagement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowed-host-families"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mac1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auto-allocate-host"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auto-release-host"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"any-host-based-license-configuration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Outputs"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ResourceGroupARN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ResourceGroupARN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Fn::GetAtt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DedicatedHostGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;aws_cloudformation_stack&lt;/code&gt; resource will export the &lt;code&gt;DedicatedHostGroup&lt;/code&gt; attribute (see the code of CloudFromation template), which you will use later in the Launch Template resource. &lt;/p&gt;
&lt;h3&gt;
  
  
  Pro tips
&lt;/h3&gt;

&lt;p&gt;If you manage an AWS Organization, I have good news: Host groups and Licenses are supported by &lt;a href="https://docs.aws.amazon.com/ram/latest/userguide/shareable.html" rel="noopener noreferrer"&gt;Resource Access Manager&lt;/a&gt; service. Hence, you can host all mac instances in one account and share them with other accounts — it might be helpful for costs allocation, for example. Also, check out &lt;a href="https://dev.to/aws-builders/aws-resource-access-manager-simple-and-powerful-service-for-multi-account-resource-governance-11na"&gt;my blog about AWS RAM&lt;/a&gt; if you are very new to this service.&lt;/p&gt;

&lt;p&gt;To solve the “which AZ supports mac metal” puzzle, you can leverage the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_instance_type_offerings" rel="noopener noreferrer"&gt;aws_ec2_instance_type_offerings&lt;/a&gt; and &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids" rel="noopener noreferrer"&gt;aws_subnet_ids&lt;/a&gt; data sources.&lt;/p&gt;
&lt;h2&gt;
  
  
  Costs considerations
&lt;/h2&gt;

&lt;p&gt;License Manager is a &lt;a href="https://aws.amazon.com/license-manager/pricing/" rel="noopener noreferrer"&gt;free of charge service&lt;/a&gt;, as well as &lt;a href="https://aws.amazon.com/autoscaling/pricing/" rel="noopener noreferrer"&gt;Auto Scaling&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/about-aws/whats-new/2017/11/introducing-launch-templates-for-amazon-ec2-instances/" rel="noopener noreferrer"&gt;Launch Template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So it’s all about the price for mac1.metal Dedicated Host which is &lt;a href="https://aws.amazon.com/ec2/dedicated-hosts/pricing/" rel="noopener noreferrer"&gt;$1.083 per hour&lt;/a&gt; as of October 2021. However, &lt;a href="https://docs.aws.amazon.com/savingsplans/latest/userguide/what-is-savings-plans.html" rel="noopener noreferrer"&gt;Saving Plans&lt;/a&gt; can be applied.&lt;/p&gt;

&lt;p&gt;Please note that the minimum allocation time for that type of host is 24 hours. Maybe someday AWS will change that to 1-hour minimum (fingers crossed).&lt;/p&gt;
&lt;h2&gt;
  
  
  Oh. So. ASG.
&lt;/h2&gt;

&lt;p&gt;The Auto Scaling for mac1.metal opens new possibilities for CI/CD: you can integrate that to your favorite tool (GitLab, Jenkins, whatsoever) using AWS Lambda and provision new instances when your development/testing environments need that. Or you can use other cool ASG stuff, such as Lifecycle hooks, to create even more custom scenarios.&lt;/p&gt;

&lt;p&gt;Considering the “hidden” (undocumented) nature of the described setup, I suggest treating it as rather testing than production-ready for now. However, my tests show that everything works pretty well: hosts are allocated, instances are spawned, and the monthly bill grows.&lt;/p&gt;

&lt;p&gt;I suppose AWS will officially announce all this in the nearest future. Along with that, I am looking forward to the announcement of Monterey-based AMIs and maybe even M1 chip-based instances (will it be mac2.metal?).&lt;/p&gt;

&lt;p&gt;And I want to say thanks (thanks, pal!) to &lt;a href="https://github.com/hashicorp/terraform/issues/28531" rel="noopener noreferrer"&gt;OliverKoo&lt;/a&gt;, who started digging into that back in April'21. &lt;/p&gt;




&lt;div class="ltag__user ltag__user__id__51518"&gt;
    &lt;a href="/svasylenko" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51518%2F4afa166d-ed44-41a7-a76e-4e5b14b4dde1.jpeg" alt="svasylenko image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/svasylenko"&gt;Serhii Vasylenko&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/svasylenko"&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>aws</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
