<?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: Raj Patil</title>
    <description>The latest articles on DEV Community by Raj Patil (@raj-glitch-max).</description>
    <link>https://dev.to/raj-glitch-max</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%2F3911373%2Feb558ffc-4b70-4cdf-b6c2-79e9109e05d9.png</url>
      <title>DEV Community: Raj Patil</title>
      <link>https://dev.to/raj-glitch-max</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raj-glitch-max"/>
    <language>en</language>
    <item>
      <title>I got tired of manually digging CloudTrail every time Terraform drifted — so I built tf-why</title>
      <dc:creator>Raj Patil</dc:creator>
      <pubDate>Mon, 04 May 2026 12:34:46 +0000</pubDate>
      <link>https://dev.to/raj-glitch-max/i-got-tired-of-manually-digging-cloudtrail-every-time-terraform-drifted-so-i-built-tf-why-3dml</link>
      <guid>https://dev.to/raj-glitch-max/i-got-tired-of-manually-digging-cloudtrail-every-time-terraform-drifted-so-i-built-tf-why-3dml</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every time &lt;code&gt;terraform plan&lt;/code&gt; showed drift, I'd see something like this:&lt;br&gt;
~ aws_security_group.web will be updated in-place&lt;br&gt;
~ ingress = [&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;{&lt;/li&gt;
&lt;li&gt;from_port = 22&lt;/li&gt;
&lt;li&gt;protocol = "tcp"&lt;/li&gt;
&lt;li&gt;to_port = 22
},
]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform tells you &lt;em&gt;what&lt;/em&gt; changed. It tells you &lt;em&gt;nothing&lt;/em&gt; about who &lt;br&gt;
changed it, when, or how.&lt;/p&gt;

&lt;p&gt;So I'd open the AWS Console, navigate to CloudTrail, set the time range, &lt;br&gt;
filter by resource, scroll through dozens of unrelated events, and &lt;br&gt;
eventually find the culprit — 20 minutes later.&lt;/p&gt;

&lt;p&gt;Every. Single. Time.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tf-why&lt;/code&gt; — a CLI that pipes &lt;code&gt;terraform show -json&lt;/code&gt; output directly into &lt;br&gt;
AWS CloudTrail and gives you attribution in seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform show &lt;span class="nt"&gt;-json&lt;/span&gt; plan.tfplan | tf-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
aws_security_group.web (ingress rules changed)&lt;br&gt;
├── Changed by: &lt;a href="mailto:john.doe@company.com"&gt;john.doe@company.com&lt;/a&gt;&lt;br&gt;
├── When: 2 days ago (2026-05-02 14:32 UTC)&lt;br&gt;
├── Via: AWS Console&lt;br&gt;
└── Event: AuthorizeSecurityGroupIngress&lt;/p&gt;

&lt;p&gt;aws_s3_bucket.assets (versioning changed)&lt;br&gt;
├── Changed by: ci-deploy-role&lt;br&gt;
├── When: 1 day ago (2026-05-03 09:15 UTC)&lt;br&gt;
├── Via: AWS CLI / SDK&lt;br&gt;
└── Event: PutBucketVersioning&lt;/p&gt;

&lt;p&gt;2 drifted resources found.&lt;/p&gt;
&lt;h2&gt;
  
  
  That's it.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  How it works internally
&lt;/h2&gt;

&lt;p&gt;Three steps:&lt;br&gt;
&lt;strong&gt;1. Parse the plan JSON&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;terraform show -json plan.tfplan&lt;/code&gt; outputs clean structured JSON.&lt;br&gt;
I filter &lt;code&gt;resource_changes[]&lt;/code&gt; for entries where &lt;code&gt;change.actions&lt;/code&gt; &lt;br&gt;
includes &lt;code&gt;"update"&lt;/code&gt; — these are your drifted resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Map resource to CloudTrail lookup key&lt;/strong&gt;&lt;br&gt;
Each Terraform resource type maps to a real AWS resource identifier.&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aws_s3_bucket.my_bucket&lt;/code&gt; → bucket name from &lt;code&gt;change.after.bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws_security_group.web&lt;/code&gt; → security group ID from &lt;code&gt;change.after.id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I maintain a mapper for 25+ resource types covering S3, EC2, IAM, &lt;br&gt;
RDS, Lambda, ECS, EKS, DynamoDB, SQS, SNS, CloudFront, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Query CloudTrail&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LookupAttributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AttributeKey&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ResourceName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AttributeValue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resource_id&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;StartTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lookback&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;EndTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;Filter for write events on the drifted resource → sort by time → &lt;/p&gt;

&lt;h2&gt;
  
  
  return the most recent match.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What it doesn't do (being honest)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS only for v1&lt;/strong&gt; — Azure Activity Log and GCP Audit Log support 
is on the roadmap, community PRs welcome&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;90-day CloudTrail limit&lt;/strong&gt; — free tier CloudTrail only stores 
90 days of event history. Documented upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not 100% perfect ARN matching&lt;/strong&gt; — some complex resources with 
nested ARN formats are still being expanded
---
## Install
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;tf-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.8+&lt;/li&gt;
&lt;li&gt;AWS credentials with CloudTrail read access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - &lt;code&gt;terraform show -json plan.tfplan&lt;/code&gt; output
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why open source
&lt;/h2&gt;

&lt;p&gt;Because every team using Terraform hits this exact wall during &lt;br&gt;
incident response and post-mortems. This should exist as a standard &lt;br&gt;
part of the Terraform workflow — not buried inside paid SaaS platforms.&lt;/p&gt;

&lt;p&gt;Built this solo. 101 tests, 81% coverage.&lt;br&gt;
Roast it, break it, open issues, send PRs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Raj-glitch-max/tf.why" rel="noopener noreferrer"&gt;https://github.com/Raj-glitch-max/tf.why&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/tf-why" rel="noopener noreferrer"&gt;https://pypi.org/project/tf-why&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;What's the most painful part of your Terraform debugging workflow? &lt;br&gt;
Genuinely curious — next feature might solve yours.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>cloud</category>
      <category>terraform</category>
    </item>
  </channel>
</rss>
