<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Luthfi Anandra's Blog]]></title><description><![CDATA[My personal blog that discussed about cloud/cloud native engineering, infrastructure and technology]]></description><link>https://blog.lanandra.my.id</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 17:59:09 GMT</lastBuildDate><atom:link href="https://blog.lanandra.my.id/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Migrate Terraform state locking from DynamoDB to S3]]></title><description><![CDATA[Terraform state locking as referred from Terraform State Locking documentation is a feature where Terraform will lock your state for all operations that could write state. This prevents others from acquiring the lock and potentially corrupting your s...]]></description><link>https://blog.lanandra.my.id/migrate-terraform-state-locking-from-dynamodb-to-s3</link><guid isPermaLink="true">https://blog.lanandra.my.id/migrate-terraform-state-locking-from-dynamodb-to-s3</guid><category><![CDATA[Terraform]]></category><category><![CDATA[S3]]></category><category><![CDATA[DynamoDB]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Luthfi Anandra]]></dc:creator><pubDate>Wed, 02 Apr 2025 02:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743552849503/bc724b95-1628-4e7e-9ab9-a3ae34bd22a4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Terraform state locking as referred from <a target="_blank" href="https://developer.hashicorp.com/terraform/language/state/locking">Terraform State Locking</a> documentation is a feature where Terraform will lock your state for all operations that could write state. This prevents others from acquiring the lock and potentially corrupting your state. Please remember not all backends support locking as mentioned in the Terraform State Locking documentation.</p>
<p>If we deploy infrastructure in Amazon Web Services (AWS), one of the backend that we can use is S3. As referred to <a target="_blank" href="https://developer.hashicorp.com/terraform/language/backend/s3#state-locking">S3 State Locking</a> documentation, state locking is an opt-in feature in S3 backend. Previously, state locking only can be enabled via DynamoDB. But since Terraform <a target="_blank" href="https://github.com/hashicorp/terraform/releases/tag/v1.11.0">v1.11.0</a>, Terraform S3 native state locking is generally available, so that we can enable state locking in S3 or DynamoDB. However, as stated in S3 State Locking documentation, DynamoDB based locking is deprecated and will be removed in a future minor version.</p>
<p>In this blog, I will explain how to migrate Terraform state locking from DynamoDB to S3 native state locking. In the example, I will run Terraform workflow both from CI/CD and local workstation, only for demo purpose. I use Amazon CodeCatalyst to run my Terraform workflow in CI/CD pipeline. But you may use other CI/CD tool or maybe running locally just to follow along how S3 state locking works with some adjustment on your side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743552889126/3e265942-2ee3-47bb-97f6-4d10b8d88850.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Disclaimer: All contents written in this blog are solely my personal opinion and doesn’t represent my employer opinion</p>
</blockquote>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Here are the prerequisites that needed to follow configurations discussed in this blog:</p>
<ol>
<li><p>AWS account</p>
</li>
<li><p>Preconfigured S3 bucket and DynamoDB table that will be used for Terraform backend migration</p>
</li>
</ol>
<h1 id="heading-configuration-steps">Configuration steps</h1>
<h2 id="heading-reconfigure-and-migrate-terraform-state">Reconfigure and migrate Terraform state</h2>
<ol>
<li>As an example, I have existing Terraform backend configuration with DynamoDB state locking as referred with argument <code>dynamodb_table</code></li>
</ol>
<pre><code class="lang-go">terraform {
  backend <span class="hljs-string">"s3"</span> {
    bucket         = <span class="hljs-string">"my-tfbucket"</span>
    key            = <span class="hljs-string">"path/to/terraform.tfstate"</span>
    region         = <span class="hljs-string">"ap-southeast-1"</span>
    dynamodb_table = <span class="hljs-string">"my-tflocks"</span>
    encrypt        = <span class="hljs-literal">true</span>
  }
}
</code></pre>
<ol start="2">
<li>Then I changed the structure of Terraform backend. I removed argument <code>dynamodb_table</code> and added argument <code>use_lockfile</code> and I set the value to <code>true</code></li>
</ol>
<pre><code class="lang-go">terraform {
  backend <span class="hljs-string">"s3"</span> {
    bucket       = <span class="hljs-string">"my-tfbucket"</span>
    key          = <span class="hljs-string">"path/to/terraform.tfstate"</span>
    region       = <span class="hljs-string">"ap-southeast-1"</span>
    encrypt      = <span class="hljs-literal">true</span>
    use_lockfile = <span class="hljs-literal">true</span>
  }
}
</code></pre>
<ol start="3">
<li><p>Run <code>terraform init</code> to trigger backend changes after using S3 native state locking. In my case, I run it on my local with reason this just one time activity and I prefer to make it simple. But if you want to trigger the changes with different method, for example run in CI/CD, you may try it as well</p>
</li>
<li><p>First run of <code>terraform init</code> may be failed with error that stated that backend configuration is changed</p>
</li>
</ol>
<pre><code class="lang-plaintext">✗ terraform init
Initializing the backend...
Initializing modules...
╷
│ Error: Backend configuration changed
│ 
│ A change in the backend configuration has been detected, which may require migrating existing state.
│ 
│ If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
│ If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".
</code></pre>
<ol start="5">
<li>Re-run <code>terraform init</code>, add args <code>-reconfigure</code> so that terraform will reconfigure and migrate your backend with new configuration that used S3 native state locking</li>
</ol>
<pre><code class="lang-plaintext">✗ terraform init -reconfigure
Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing modules...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.47.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
</code></pre>
<ol start="6">
<li>For full context of code example, you can check this <a target="_blank" href="https://github.com/lanandra/mock-aws-infrastructure/blob/master/sandbox/sbx0/ec2/backend.tf">file</a> inside this <a target="_blank" href="https://github.com/lanandra/mock-aws-infrastructure/tree/master/sandbox/sbx0/ec2">directory</a></li>
</ol>
<h2 id="heading-test-terraform-s3-state-locking">Test Terraform S3 state locking</h2>
<p>In this blog, I simulate Terraform S3 state locking by running multiple Terraform plan in the same period. One is ran in CI/CD, in my case is Amazon CodeCatalyst. And the other one is ran from my local to simplify testing process. But you may test with different test case and in real environment, more likely We will experience running multiple Terraform plan from multiple CI/CD pipelines.</p>
<ol>
<li>Here is the example when I ran terraform plan in my CI/CD but in the same time I also ran terraform plan from my local workstation. Terraform plan will be failed</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743274566097/50b0e713-869f-4e3d-9472-2615ab79f670.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li>Terraform plan got error message: Error acquiring the state lock</li>
</ol>
<pre><code class="lang-plaintext">Error: Error acquiring the state lock

Error message: operation error S3: PutObject, https response error
StatusCode: 412, RequestID: 12345ABCDE, HostID:
1a2b/3c4d/5e==,
api error PreconditionFailed: At least one of the pre-conditions you
specified did not hold
Lock Info:
ID:        1a2b3c4d5e-3ec1-1f71-9cdd-1a2b3c4d5e
Path:      path/to/terraform.tfstate
Operation: OperationTypePlan
Who:       lanandra@Luthfis-MacBook-Pro.local
Version:   1.11.2
Created:   2025-03-22 18:56:18.872219 +0000 UTC
Info:


Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.
</code></pre>
<ol start="3">
<li>In this case, We can verify that Terraform state locking in S3 has been working as it will prevent multiple terraform plan run and overriding the state in the same period</li>
</ol>
<h1 id="heading-summary">Summary</h1>
<p>We have reached the last section of this blog. Here are some key takeaways that can be summarized:</p>
<ol>
<li><p>Terraform state locking is a feature where Terraform will lock your state for all operations that could write state and prevent others from acquiring the lock and potentially corrupting your state</p>
</li>
<li><p>If we are using S3 as our Terraform backend, previously state locking only support DynamoDB but since Terraform v1.11.0, state locking also support S3 native state locking</p>
</li>
<li><p>DynamoDB based locking is deprecated and will be removed in a future minor version</p>
</li>
</ol>
<p>Please comment if you have any suggestions, critiques, or thoughts.</p>
<p>Hope this article will benefit you. Thank you!</p>
]]></content:encoded></item><item><title><![CDATA[Leveraging Cloud Custodian to shift left tag compliance]]></title><description><![CDATA[Tagging is one of the capability that included in Establishing Your Cloud Foundation on AWS whitepaper under category Governance, Risk Management, and Compliance.

Tagging enables you to group sets of resources by assigning metadata to cloud resource...]]></description><link>https://blog.lanandra.my.id/leveraging-cloud-custodian-to-shift-left-tag-compliance</link><guid isPermaLink="true">https://blog.lanandra.my.id/leveraging-cloud-custodian-to-shift-left-tag-compliance</guid><category><![CDATA[cloudcustodian]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[Luthfi Anandra]]></dc:creator><pubDate>Sat, 01 Feb 2025 17:00:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737980213048/9165871f-fe79-44cc-bb58-528882cd6e2a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Tagging is one of the capability that included in Establishing Your Cloud Foundation on AWS <a target="_blank" href="https://docs.aws.amazon.com/whitepapers/latest/establishing-your-cloud-foundation-on-aws/governance.html">whitepaper</a> under category Governance, Risk Management, and Compliance.</p>
<blockquote>
<p><strong>Tagging</strong> enables you to group sets of resources by assigning metadata to cloud resources for a variety of purposes. These purposes include access control (such as ABAC), cost reporting, and automation (such as patching for select tagged instances). Tagging can also be used to create new resource constructs for visibility or control (such as grouping together resources that make up a microservice, application, or workload). Tagging is fundamental to providing enterprise-level visibility and control.</p>
</blockquote>
<p>There are several use cases why we need tagging as explained in this document “<a target="_blank" href="https://docs.aws.amazon.com/whitepapers/latest/tagging-best-practices/tagging-use-cases.html">Tagging use cases</a>“, among them:</p>
<ol>
<li><p>Tags for cost allocation and financial management</p>
</li>
<li><p>Tags for operations and support</p>
</li>
<li><p>Tags for data security, risk management, and access control</p>
</li>
</ol>
<p>In this blog, I will explain how to shift left tag compliance check inside CI/CD. I leverage Cloud Custodian package called c7n-left to run policy directly against Infrastructure as Code which in this case is Terraform. If tag compliance check is passed then Terraform core workflow which consist plan and apply can be continued. Otherwise, if tag compliance check is failed then Terraform core workflow cannot be continued. CI/CD tool that I used in this blog is Amazon CodeCatalyst.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737996890235/5bb19798-2ff1-41b2-8710-0532a21347f8.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Disclaimer: All contents written in this blog are solely my personal opinion and doesn’t represent my employer opinion</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Here are the prerequisites that needed to follow configurations discussed in this blog:</p>
<ol>
<li><p>AWS account</p>
</li>
<li><p>CodeCatalyst space and project that has been connected with AWS account and source repository. In this blog, I use GitHub as source repository. For configuration reference, please check my other blog "<a target="_blank" href="https://medium.com/aws-in-plain-english/build-and-release-container-image-to-amazon-elastic-container-registry-ecr-via-amazon-ae7f9f07a135"><strong>Build and Release Container Image to Amazon Elastic Container Registry (ECR) via Amazon CodeCatalyst</strong></a>"</p>
</li>
<li><p>S3 bucket and DynamoDB table that will be used for Terraform backend</p>
</li>
</ol>
<h2 id="heading-configuration-steps"><strong>Configuration steps</strong></h2>
<h3 id="heading-directory-structure"><strong>Directory structure</strong></h3>
<p>Below is directory structure used in this blog:</p>
<pre><code class="lang-bash">.
├── .codecatalyst/
│   └── workflows/
│       └── tf-sandbox-sbx0-ec2.yml
├── custodian/
│   └── tf-tag-compliance.yml
└── sandbox/
    └── sbx0/
        └── ec2/
            ├── backend.tf
            ├── main.tf
            ├── terraform.tfvars
            └── variables.tf
</code></pre>
<h3 id="heading-custodian-policy">Custodian policy</h3>
<p>We will start discussion with policy that will be used to evaluate Infrastructure as Code by Cloud Custodian</p>
<ol>
<li>Under directory <code>custodian</code>, I created file called <code>tf-tag-compliance.yml</code>. Inside that file there is a policy named <code>check-tag</code> that will check all Terraform codes that related to AWS resources. Policy will check if tag called <code>lanandra:environment</code> is absent or not on all resources that taggable. I also added metadata to that policy</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">policies:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">check-tag</span>
    <span class="hljs-attr">resource:</span> <span class="hljs-string">"terraform.aws*"</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">category:</span> [<span class="hljs-string">tag</span>]
      <span class="hljs-attr">severity:</span> <span class="hljs-string">low</span>
    <span class="hljs-attr">filters:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">taggable</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">or:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">value</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"tag:lanandra:environment"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">absent</span>
</code></pre>
<h3 id="heading-sample-terraform-code">Sample Terraform code</h3>
<p>As an example, I created two EC2 instances, one that has tag <code>lanandra:environment</code> and the other doesn’t</p>
<ol>
<li><p>Create Terraform files inside directory <code>sandbox/sbx0/ec2</code> , main resources such as EC2 are defined inside <code>main.tf</code> file</p>
</li>
<li><p>EC2 that has tag <code>lanandra:environment</code> , this tag is got by merging local tags.</p>
</li>
</ol>
<pre><code class="lang-go">locals {
  azs = slice(data.aws_availability_zones.available.names, <span class="hljs-number">0</span>, <span class="hljs-number">3</span>)

  tags = {
    <span class="hljs-string">"lanandra:environment"</span> = <span class="hljs-string">"${var.env_name}"</span>
    <span class="hljs-string">"lanandra:managedBy"</span>   = <span class="hljs-string">"terraform"</span>
  }
}

module <span class="hljs-string">"lab_ec2_0"</span> {
  source  = <span class="hljs-string">"terraform-aws-modules/ec2-instance/aws"</span>
  version = <span class="hljs-string">"5.6.1"</span>

  name = <span class="hljs-string">"${var.env_name}-lab-0"</span>

  ami                         = data.aws_ami.ubuntu_22_04.id
  instance_type               = <span class="hljs-string">"t3a.nano"</span>
  availability_zone           = element(local.azs, <span class="hljs-number">0</span>)
  subnet_id                   = element(<span class="hljs-keyword">var</span>.public_subnet_id, <span class="hljs-number">0</span>)
  vpc_security_group_ids      = [module.lab_sg.security_group_id]
  associate_public_ip_address = <span class="hljs-literal">true</span>
  key_name                    = <span class="hljs-keyword">var</span>.env_name
  iam_instance_profile        = module.lab_instance_profile.iam_instance_profile_name

  enable_volume_tags = <span class="hljs-literal">false</span>
  root_block_device = [
    {
      encrypted   = <span class="hljs-literal">true</span>
      volume_type = <span class="hljs-string">"gp3"</span>
      volume_size = <span class="hljs-number">10</span>
      tags = merge(
        local.tags,
        {
          Name = <span class="hljs-string">"${var.env_name}-lab-0"</span>
        }
      )
    }
  ]

  tags = merge(
    local.tags,
    {
      Name = <span class="hljs-string">"${var.env_name}-lab-0"</span>
    }
  )
}
</code></pre>
<ol start="2">
<li>EC2 that hasn’t got tag <code>lanandra:environment</code></li>
</ol>
<pre><code class="lang-go">module <span class="hljs-string">"lab_ec2_1"</span> {
  source  = <span class="hljs-string">"terraform-aws-modules/ec2-instance/aws"</span>
  version = <span class="hljs-string">"5.6.1"</span>

  name = <span class="hljs-string">"${var.env_name}-lab-1"</span>

  ami                         = data.aws_ami.ubuntu_22_04.id
  instance_type               = <span class="hljs-string">"t3a.nano"</span>
  availability_zone           = element(local.azs, <span class="hljs-number">0</span>)
  subnet_id                   = element(<span class="hljs-keyword">var</span>.public_subnet_id, <span class="hljs-number">0</span>)
  vpc_security_group_ids      = [module.lab_sg.security_group_id]
  associate_public_ip_address = <span class="hljs-literal">true</span>
  key_name                    = <span class="hljs-keyword">var</span>.env_name
  iam_instance_profile        = module.lab_instance_profile.iam_instance_profile_name

  enable_volume_tags = <span class="hljs-literal">false</span>
  root_block_device = [
    {
      encrypted   = <span class="hljs-literal">true</span>
      volume_type = <span class="hljs-string">"gp3"</span>
      volume_size = <span class="hljs-number">10</span>
      tags = {
        Name = <span class="hljs-string">"${var.env_name}-lab-1"</span>
      }
    }
  ]

  tags = {
    Name = <span class="hljs-string">"${var.env_name}-lab-1"</span>
  }
}
</code></pre>
<ol start="3">
<li>For full context of Terraform codes/files, please refer to this <a target="_blank" href="https://github.com/lanandra/mock-aws-infrastructure/tree/master/sandbox/sbx0/ec2"><strong>directory</strong></a></li>
</ol>
<h3 id="heading-run-tag-compliance-in-cicd-pipeline">Run tag compliance in CI/CD pipeline</h3>
<p>As mentioned in the beginning of this blog, I use Amazon CodeCatalyst as tool for CI/CD pipeline.</p>
<ol>
<li>I created a workflow file called <code>tf-sandbox-sbx0-ec2.yml</code> and I put that inside <code>.codecatalyst/workflow</code> directory. Inside workflow/pipeline, I created action called <code>tag-compliance</code>. This action use container image <code>python:3.12.8-bookworm</code> that being pulled from Dockerhub. First, it will redirect working directory to ec2 directory (<code>sandbox/sbx0/ec2</code>). Then it will run several steps that needed to run tag compliance check using Cloud Custodian. In this case, I use specific package called <code>c7n-left</code> that designed to evaluate policies directly against infrastructure as code source assets (Please see <a target="_blank" href="https://cloudcustodian.io/docs/tools/c7n-left.html#custodian-policies-for-infrastructure-code">Custodian policies for Infrastructure Code</a> for more details). Then it will run command that will find custodian policy inside <code>custodian</code> directory with category value filtered to tag</li>
</ol>
<pre><code class="lang-yaml">  <span class="hljs-attr">tag-compliance:</span>
    <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
    <span class="hljs-attr">Inputs:</span>
      <span class="hljs-attr">Sources:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
    <span class="hljs-attr">Environment:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
      <span class="hljs-attr">Connections:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
          <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
    <span class="hljs-attr">Configuration:</span>
      <span class="hljs-attr">Container:</span>
        <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
        <span class="hljs-attr">Image:</span> <span class="hljs-string">python:3.12.8-bookworm</span>
      <span class="hljs-attr">Steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">c7n</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">c7n-left</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">c7n-left</span> <span class="hljs-string">run</span> <span class="hljs-string">-p</span> <span class="hljs-string">../../../custodian/</span> <span class="hljs-string">-d</span> <span class="hljs-string">.</span> <span class="hljs-string">--filters="category=tag"</span>
    <span class="hljs-attr">Compute:</span>
      <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
</code></pre>
<ol start="2">
<li>In workflow file, tag compliance action will be merged with other actions that used by Terraform core workflow. For full context, please see this <a target="_blank" href="https://github.com/lanandra/mock-aws-infrastructure/blob/master/.codecatalyst/workflows/tf-sandbox-sbx0-ec2.yml">file</a>:</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">Name:</span> <span class="hljs-string">tf-sandbox-sbx0-ec2</span>
<span class="hljs-attr">SchemaVersion:</span> <span class="hljs-string">"1.0"</span>

<span class="hljs-attr">Triggers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Type:</span> <span class="hljs-string">PUSH</span>
    <span class="hljs-attr">Branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
    <span class="hljs-attr">FilesChanged:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sandbox\/sbx0\/ec2\/.*</span>

<span class="hljs-attr">Actions:</span>
  <span class="hljs-attr">tag-compliance:</span>
    <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
    <span class="hljs-attr">Inputs:</span>
      <span class="hljs-attr">Sources:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
    <span class="hljs-attr">Environment:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
      <span class="hljs-attr">Connections:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
          <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
    <span class="hljs-attr">Configuration:</span>
      <span class="hljs-attr">Container:</span>
        <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
        <span class="hljs-attr">Image:</span> <span class="hljs-string">python:3.12.8-bookworm</span>
      <span class="hljs-attr">Steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">c7n</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">c7n-left</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">c7n-left</span> <span class="hljs-string">run</span> <span class="hljs-string">-p</span> <span class="hljs-string">../../../custodian/</span> <span class="hljs-string">-d</span> <span class="hljs-string">.</span> <span class="hljs-string">--filters="category=tag"</span>
    <span class="hljs-attr">Compute:</span>
      <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
  <span class="hljs-attr">terraform-plan:</span>
    <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
    <span class="hljs-attr">DependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">tag-compliance</span>
    <span class="hljs-attr">Inputs:</span>
      <span class="hljs-attr">Sources:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
    <span class="hljs-attr">Environment:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
      <span class="hljs-attr">Connections:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
          <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
    <span class="hljs-attr">Configuration:</span>
      <span class="hljs-attr">Container:</span>
        <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
        <span class="hljs-attr">Image:</span> <span class="hljs-string">hashicorp/terraform:1.8.2</span>
      <span class="hljs-attr">Steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">fmt</span> <span class="hljs-string">-check</span> <span class="hljs-string">-no-color</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span> <span class="hljs-string">-no-color</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">validate</span> <span class="hljs-string">-no-color</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">-no-color</span> <span class="hljs-string">-input=false</span>
    <span class="hljs-attr">Compute:</span>
      <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
  <span class="hljs-attr">wait-for-approval:</span>
    <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/approval@v1</span>
    <span class="hljs-attr">DependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">terraform-plan</span>
    <span class="hljs-attr">Configuration:</span>
      <span class="hljs-attr">ApprovalsRequired:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">terraform-apply:</span>
    <span class="hljs-attr">DependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">wait-for-approval</span>
    <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
    <span class="hljs-attr">Inputs:</span>
      <span class="hljs-attr">Sources:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
    <span class="hljs-attr">Environment:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
      <span class="hljs-attr">Connections:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
          <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
    <span class="hljs-attr">Configuration:</span>
      <span class="hljs-attr">Container:</span>
        <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
        <span class="hljs-attr">Image:</span> <span class="hljs-string">hashicorp/terraform:1.8.2</span>
      <span class="hljs-attr">Steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span> <span class="hljs-string">-no-color</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">apply</span> <span class="hljs-string">-auto-approve</span> <span class="hljs-string">-no-color</span> <span class="hljs-string">-input=false</span>
    <span class="hljs-attr">Compute:</span>
      <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
</code></pre>
<ol start="3">
<li>We will simulate to provision EC2 with tag that needed by tag compliance first. Tag compliance action will run first as defined in workflow. Cause tag compliance is passed, then Terraform core workflow can be continued. Please see detailed logs of tag compliance to see the result</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738390760380/9f555db9-9dce-42ee-9f70-296dc2b79b5b.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li>Here is the example result of successful tag compliance check using c7n-left. It said no failed result</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738390915813/7de58c09-649d-4ea9-80de-e32afd85dc87.png" alt class="image--center mx-auto" /></p>
<ol start="5">
<li>As explained in point number 3, if tag compliance action is passed, then Terraform core workflow can be continued. So that it can run terraform apply to provision EC2</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738406329729/30283eb8-b599-4572-b58f-0f870913e5a3.png" alt class="image--center mx-auto" /></p>
<ol start="6">
<li>For next example, let’s see what if we want to provision EC2 that hasn’t got tag that needed by tag compliance action. In CI/CD, tag compliance action will be failed and the rest of Terraform core workflow cannot be continued. Please see detailed logs of tag compliance action for further details</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738406416295/8feb1956-0228-4016-83b9-0d56a62809a3.png" alt class="image--center mx-auto" /></p>
<ol start="7">
<li>Custodian will inform which file(s) and line(s) that has got policy violation</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738406612303/7dfa0563-e338-4874-a918-3dea7fe8bea2.png" alt class="image--center mx-auto" /></p>
<ol start="8">
<li>Custodian will also inform how many resources that failed and got policy violation and how many resources that passed in evaluation summary</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738410774220/82064f8b-e52b-41ab-9fb0-725ebc489ecc.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-summary">Summary</h2>
<p>We have reached the last section of this blog. Here are some key takeaways that can be summarized:</p>
<ol>
<li><p>We can shift left our tag compliance check inside CI/CD</p>
<ol>
<li><p>Cloud Custodian is one of the tool that can be leveraged to run tag compliance check</p>
</li>
<li><p>Cloud Custodian has specific package called c7n-left to run policy check directly against Infrastructure code</p>
</li>
</ol>
</li>
</ol>
<p>Please comment if you have any suggestions, critiques, or thoughts.</p>
<p>Hope this article will benefit you. Thank you!</p>
]]></content:encoded></item><item><title><![CDATA[Declaratively import existing AWS resource into Terraform with CodeCatalyst workflow]]></title><description><![CDATA[Provisioning Amazon Web Services (AWS) resources came in many ways. One of them is create the resource from the console or some might say "clickops". But how if We want our resources that created in clickops way, later maintained in more codified way...]]></description><link>https://blog.lanandra.my.id/declaratively-import-existing-aws-resource-into-terraform-with-codecatalyst-workflow</link><guid isPermaLink="true">https://blog.lanandra.my.id/declaratively-import-existing-aws-resource-into-terraform-with-codecatalyst-workflow</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[Luthfi Anandra]]></dc:creator><pubDate>Sun, 15 Dec 2024 00:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734017650432/6c9d257a-b163-48f3-9e4f-4488158487d0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Provisioning Amazon Web Services (AWS) resources came in many ways. One of them is create the resource from the console or some might say "clickops". But how if We want our resources that created in clickops way, later maintained in more codified way or in Infrastructure as Code (IaC)?</p>
<p>In this blog, I will explain how to import existing AWS resource, specifically EC2 instance that provisioned using clickops way. Then, that EC2 instance will be imported into Terraform code via CI/CD pipeline. In this case, I am using Amazon CodeCatalyst as CI/CD tool. The goal is that EC2 instance later will be maintained from Terraform code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733935768003/803092f3-830b-4c8d-870b-fecdcab7f508.png" alt="workflow" class="image--center mx-auto" /></p>
<p>In real world scenario, import is not only for EC2 but it could be any other resources that supported by AWS provider</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Here are the prerequisites that needed to follow configurations discussed in this blog:</p>
<ol>
<li><p>AWS account</p>
</li>
<li><p>CodeCatalyst space and project that has been connected with AWS account and source repository. In this blog, I use GitHub as source repository. For configuration reference, please check my other blog "<a target="_blank" href="https://medium.com/aws-in-plain-english/build-and-release-container-image-to-amazon-elastic-container-registry-ecr-via-amazon-ae7f9f07a135">Build and Release Container Image to Amazon Elastic Container Registry (ECR) via Amazon CodeCatalyst</a>"</p>
</li>
<li><p>S3 bucket and DynamoDB table that will used for Terraform backend</p>
</li>
</ol>
<h2 id="heading-configuration-steps">Configuration steps</h2>
<h3 id="heading-directory-structure">Directory structure</h3>
<p>Below is directory structure used in this blog:</p>
<pre><code class="lang-bash">.
├── .codecatalyst/
│   └── workflows/
│       └── tf-sandbox-sbx0-ec2.yml
└── sandbox/
    └── sbx0/
        └── ec2/
            ├── backend.tf
            ├── main.tf
            ├── terraform.tfvars
            └── variables.tf
</code></pre>
<h3 id="heading-terraform-import-code">Terraform import code</h3>
<p>As an example, we will create terraform import code for EC2 instance:</p>
<ol>
<li><p>EC2 instance configuration may vary, but we can adjust the configuration so that there is no drift when import. The main point we need is EC2 instance id, as an example, i have created EC2 instance called <code>lab-import-tf</code> and it has its own instance id</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172035735/7645af94-1981-4474-a5a5-3f1f583d2e21.png" alt="ec2-clickops" class="image--center mx-auto" /></p>
</li>
<li><p>Create resource block for EC2. In <code>main.tf</code>, i created EC2 resource with reference from public EC2 module</p>
<pre><code class="lang-go"> module <span class="hljs-string">"lab_import_tf"</span> {
   source  = <span class="hljs-string">"terraform-aws-modules/ec2-instance/aws"</span>
   version = <span class="hljs-string">"5.6.1"</span>

   name = <span class="hljs-string">"lab-import-tf"</span> # change with name that used by your ec2 instance

   ami                         = <span class="hljs-string">"ami-0695dfe470b88c986"</span> # change with ami id that used by your ec2 instance
   instance_type               = <span class="hljs-string">"t3a.nano"</span>              # change with instance <span class="hljs-keyword">type</span> that used by your ec2 instance
   availability_zone           = element(local.azs, <span class="hljs-number">0</span>)
   subnet_id                   = element(<span class="hljs-keyword">var</span>.public_subnet_id, <span class="hljs-number">0</span>)
   vpc_security_group_ids      = [module.lab_sg.security_group_id]
   associate_public_ip_address = <span class="hljs-literal">true</span>
   key_name                    = <span class="hljs-keyword">var</span>.env_name
   iam_instance_profile        = module.lab_instance_profile.iam_instance_profile_name

   enable_volume_tags = <span class="hljs-literal">false</span>
   root_block_device = [
     {
       encrypted   = <span class="hljs-literal">true</span>
       volume_type = <span class="hljs-string">"gp3"</span>
       volume_size = <span class="hljs-number">10</span>
       tags = merge(
         local.tags,
         {
           Name = <span class="hljs-string">"lab-import-tf"</span>
         }
       )
     }
   ]

   tags = local.tags
 }
</code></pre>
</li>
<li><p>Create import block</p>
<blockquote>
<p>Note: import block is idempotent, meaning that applying an import action and running another plan will not generate another import action as long as that resource remains in your state. Refer to <a target="_blank" href="https://developer.hashicorp.com/terraform/language/import">Terraform import configuration language</a></p>
</blockquote>
<p> Inside import block, in <code>to</code> argument, insert module name defined in point number 2 above. And in <code>id</code> argument, insert instance id as referred to point number 1</p>
<pre><code class="lang-go"> # after <span class="hljs-keyword">import</span> has been finished, please remove this <span class="hljs-keyword">import</span> block
 <span class="hljs-keyword">import</span> {
   to = module.lab_import_tf.aws_instance.this[<span class="hljs-number">0</span>]
   id = <span class="hljs-string">"i-083e2e6033b02ae8a"</span> # change with id of your ec2 instance
 }
</code></pre>
</li>
<li><p>For full context of Terraform codes/files, please refer to this <a target="_blank" href="https://github.com/lanandra/mock-aws-infrastructure/tree/master/sandbox/sbx0/ec2">directory</a></p>
</li>
</ol>
<h3 id="heading-run-terraform-import-from-cicd-pipeline">Run terraform import from CI/CD pipeline</h3>
<p>In this blog, i use Amazon CodeCatalyst as tool for CI/CD pipeline.</p>
<ol>
<li><p>Define workflow/pipeline for Terraform workflow. In this example, I create workflow that will run terraform plan and terraform apply inside directory <code>sandbox/sbx0/ec2/</code></p>
<blockquote>
<p>For more details regading CodeCatalyst workflow, please see my other blog <a target="_blank" href="https://dev.to/aws-builders/gatekeep-codecatalyst-workflow-using-approval-1945">Gatekeep CodeCatalyst Workflow using Approval</a></p>
</blockquote>
<pre><code class="lang-yaml"> <span class="hljs-attr">Name:</span> <span class="hljs-string">tf-sandbox-sbx0-ec2</span>
 <span class="hljs-attr">SchemaVersion:</span> <span class="hljs-string">"1.0"</span>

 <span class="hljs-attr">Triggers:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">Type:</span> <span class="hljs-string">PUSH</span>
     <span class="hljs-attr">Branches:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
     <span class="hljs-attr">FilesChanged:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">sandbox\/sbx0\/ec2\/.*</span>

 <span class="hljs-attr">Actions:</span>
   <span class="hljs-attr">terraform-plan:</span>
     <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
     <span class="hljs-attr">Inputs:</span>
       <span class="hljs-attr">Sources:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
     <span class="hljs-attr">Environment:</span>
       <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
       <span class="hljs-attr">Connections:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
           <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
     <span class="hljs-attr">Configuration:</span>
       <span class="hljs-attr">Container:</span>
         <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
         <span class="hljs-attr">Image:</span> <span class="hljs-string">hashicorp/terraform:1.8.2</span>
       <span class="hljs-attr">Steps:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">fmt</span> <span class="hljs-string">-check</span> <span class="hljs-string">-no-color</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span> <span class="hljs-string">-no-color</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">validate</span> <span class="hljs-string">-no-color</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">-no-color</span> <span class="hljs-string">-input=false</span>
     <span class="hljs-attr">Compute:</span>
       <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
   <span class="hljs-attr">wait-for-approval:</span>
     <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/approval@v1</span>
     <span class="hljs-attr">DependsOn:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">terraform-plan</span>
     <span class="hljs-attr">Configuration:</span>
       <span class="hljs-attr">ApprovalsRequired:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">terraform-apply:</span>
     <span class="hljs-attr">DependsOn:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">wait-for-approval</span>
     <span class="hljs-attr">Identifier:</span> <span class="hljs-string">aws/build@v1</span>
     <span class="hljs-attr">Inputs:</span>
       <span class="hljs-attr">Sources:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">WorkflowSource</span>
     <span class="hljs-attr">Environment:</span>
       <span class="hljs-attr">Name:</span> <span class="hljs-string">sandbox</span>
       <span class="hljs-attr">Connections:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">Name:</span> <span class="hljs-string">lanandra-sandbox</span>
           <span class="hljs-attr">Role:</span> <span class="hljs-string">tf-codecatalyst-admin-sandbox</span>
     <span class="hljs-attr">Configuration:</span>
       <span class="hljs-attr">Container:</span>
         <span class="hljs-attr">Registry:</span> <span class="hljs-string">DockerHub</span>
         <span class="hljs-attr">Image:</span> <span class="hljs-string">hashicorp/terraform:1.8.2</span>
       <span class="hljs-attr">Steps:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">cd</span> <span class="hljs-string">sandbox/sbx0/ec2</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span> <span class="hljs-string">-no-color</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">Run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">apply</span> <span class="hljs-string">-auto-approve</span> <span class="hljs-string">-no-color</span> <span class="hljs-string">-input=false</span>
     <span class="hljs-attr">Compute:</span>
       <span class="hljs-attr">Type:</span> <span class="hljs-string">EC2</span>
</code></pre>
</li>
<li><p>As mentioned in section <a target="_blank" href="https://lanandra.hashnode.dev/declaratively-import-existing-aws-resource-into-terraform-with-codecatalyst-workflow#heading-terraform-import-code">Terraform import code</a>, I have created import block and resource block for EC2 that will be imported. For next action, I run CodeCatalyst workflow (tf-sandbox-sbx0-ec2) from CodeCatalyst web console. Workflow will run action <code>terraform-plan</code>, see Logs for detailed plan</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172058290/b1a92386-8956-4358-8305-49682f1b4d6d.png" alt="terraform-plan" class="image--center mx-auto" /></p>
</li>
<li><p>In the logs, <code>terraform-plan</code> will perform import action. It will import instance id that has been defined in the code</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172077605/23eef8e7-b2ba-4584-8860-b2dc0ca2f0d8.png" alt="terraform-plan-details-1" class="image--center mx-auto" /></p>
</li>
<li><p>Beside that, in <code>terraform-plan</code> summary, it said that it will import 1 item which instance id that mentioned in point number 3. But there is also 1 change. This change is related to adding tag <code>"lanandra:managedBy" = "terraform"</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172098362/12d19dfb-55e5-4603-8104-06b5575d1edf.png" alt="terraform-plan-details-1" class="image--center mx-auto" /></p>
</li>
<li><p>Why this happened ? because when I provisioned EC2 instance for the first time from web console, I didn't add tag <code>"lanandra:managedBy" = "terraform"</code>. But in my locals in <code>main.tf</code>, I declared to add that tag</p>
<pre><code class="lang-go"> locals {
   azs = slice(data.aws_availability_zones.available.names, <span class="hljs-number">0</span>, <span class="hljs-number">3</span>)

   tags = {
     <span class="hljs-string">"lanandra:environment"</span> = <span class="hljs-string">"${var.env_name}"</span>
     <span class="hljs-string">"lanandra:managedBy"</span>   = <span class="hljs-string">"terraform"</span>
   }
 }
</code></pre>
</li>
<li><p>And inside my EC2 module, I have declared tags to use tags from locals</p>
<pre><code class="lang-go"> module <span class="hljs-string">"lab_import_tf"</span> {
   source  = <span class="hljs-string">"terraform-aws-modules/ec2-instance/aws"</span>
   version = <span class="hljs-string">"5.6.1"</span>

   name = <span class="hljs-string">"lab-import-tf"</span> # change with name that used by your ec2 instance

 --redacted--
   root_block_device = [
     {
       encrypted   = <span class="hljs-literal">true</span>
       volume_type = <span class="hljs-string">"gp3"</span>
       volume_size = <span class="hljs-number">10</span>
       tags = merge(
         local.tags,
         {
           Name = <span class="hljs-string">"lab-import-tf"</span>
         }
       )
     }
   ]

   tags = local.tags
 }
</code></pre>
</li>
<li><p>After <code>terraform-plan</code> action has been finished and plan is expected, then workflow will continue to run action <code>wait-for-approval</code>. Please choose <code>approve</code> and click <code>submit</code> to continue</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734171914901/456ebdc4-c89d-43ad-8cb7-d7c78dda86dd.png" alt="wait-for-approval" class="image--center mx-auto" /></p>
</li>
<li><p>Click <code>confirm</code> button if asked for reconfirmation</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172317866/05c432e7-1ca7-42d5-b3e8-79557835bcee.png" alt="wait-for-approval-confirmation" class="image--center mx-auto" /></p>
</li>
<li><p>Then workflow will continue to run <code>terraform-apply</code>, open <code>Logs</code> for more details</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172330390/c05c7c6a-75bf-4d05-b091-7a2fc2c22525.png" alt="terraform-apply" class="image--center mx-auto" /></p>
</li>
<li><p><code>terraform-apply</code> will continue to import and add tag like what have been previewed in <code>terraform plan</code> previously</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172339962/93efdb59-abf5-4ec7-9006-20484cce9b08.png" alt="terraform-apply-details" class="image--center mx-auto" /></p>
</li>
<li><p>Afterwards, we can verify resource has been imported to terraform state. One of the method is run terraform cli command inside directory where EC2 resources resided</p>
<pre><code class="lang-bash">➜  ec2 git:(master) terraform state list | grep instance
module.lab_import_tf.aws_instance.this[0]
</code></pre>
</li>
<li><p>We also can trigger another workflow run from CodeCatalyst, terraform plan should state that there is no changes and infrastructure matches with current configuration</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734172364532/8ba7c66e-2ac6-4fce-94b7-d6ab9280cd7f.png" alt="terraform-found-no-changes" class="image--center mx-auto" /></p>
</li>
<li><p>Clean up import block that have mentioned in point number 3</p>
</li>
</ol>
<h2 id="heading-summary">Summary</h2>
<p>We have reached the last section of this blog. Here are some key takeaways that can be summarized:</p>
<ol>
<li><p>We can import existing AWS resources into Terraform codes using import block</p>
</li>
<li><p>Import block is idempotent</p>
</li>
<li><p>We can utilize CI/CD pipeline during import process</p>
<ol>
<li><p>Create history</p>
</li>
<li><p>Act as single source of truth</p>
</li>
</ol>
</li>
</ol>
<p>Please comment if you have any suggestions, critiques, or thoughts.</p>
<p>Hope this article will benefit you. Thank you!</p>
]]></content:encoded></item></channel></rss>