Get diff-formatted file from `terraform plan`

Is there any way to get a diff-formatted terraform plan output? I made an issue about this topic on GitHub: https://github.com/hashicorp/terraform/issues/23103

If there isn’t a way get such an output, would there be any interest in a PR that introduced this functionality?

Hi @artburkart!

There isn’t currently a feature for that directly. Such a thing would of course not be able to express exactly the same information as the normal terraform plan output, because not everything is described there as an addition or a removal, but I expect you’re right that it could produce “good enough” output that would be good enough for humans to read in most cases. Something like this, perhaps:

--- prior state
+++ planned new state
@@ -1,4 +0,0 @@
 resource "aws_instance" "foo" {
   ami           = "ami-abc123"
-  instance_type = "t2.micro"
+  instance_type = "m3.medium"

   root_block_device {
     etc, etc
   }

+  ebs_block_device {
+    etc, etc
+  }
 }

I guess the easiest way to get there would be to use the existing renderer from terraform show when it renders state files to render the prior state to a string and then render the planned new state to a string and then run a normal line-based string diff between those two strings to produce the conventional unified diff syntax like above.

Such a thing could potentially appear as terraform show -unified-diff planfile, building on the existing convention of using -json to get a JSON representation of the plan.

I think there are two main challenges to that:

  • I don’t think we currently have a straightforward unified diff library in the Terraform codebase. Terraform has its own diffing logic for doing a structural diff, and the string differ in there could potentially be used to get the information to build it, but producing a conventional unified diff also requires some particular handling of hunks and context which Terraform’s existing differ doesn’t need to contend with. If there is an existing library out there that is relatively straightforward (not a lot of extra code) and under a permissive license like MIT then we could consider using it, though.
  • The “planned new state” in a plan is actually not a conventional state structure like what the terraform show implementation currently deals with to show the existing state: instead, it’s an amalgamation of the state model and the plan model so that the renderer can correctly insert (known after apply) in places where values aren’t yet known. For that reason, it may not be possible to just use the existing state renderer directly and it might instead be necessary to adapt it to take an optional *plans.Changes value which, if provided, would be consulted to check for unknown values.

I think the main decider for whether we’d want to include such a thing in Terraform Core – given that it’s not been a common request and it’s not part of the primary Terraform workflow – is whether it’d be possible to implement it without adding considerably more code to maintain or without causing large disruptions to existing codepaths.

I’m not sure off the top of my head whether it’s possible, so I think the best way to proceed here would be to try prototyping it to see how it looks. If it seems like some non-trivial refactoring would be required to implement terraform show -unified-diff tfplan, or if it requires introducing a significant amount of new rendering code (vs. just using lightly-modified existing codepaths with an existing unified diff library), then I expect we’d rather look at what it would take to help support such a thing being handled in a separate tool outside of Terraform.

Thanks for starting this discussion!

This is definitely something I’d be interested in. I might start writing a wrapper like terraform-landscape that produces a diff.

Well, this is sort of partially there. It’s helpful enough for my needs:

#!/usr/bin/env bash

# Get plan
terraform plan -out=$TMPDIR/tfplan > /dev/null 2>&1

# Convert plan to json
CHANGES=$(terraform show -json $TMPDIR/tfplan | jq '.resource_changes[].change')

# Diff before and after with newlines expanded
diff -u \
  <(echo "$CHANGES" | jq '.before' | sed 's/\\n/\
/g') \
<(echo "$CHANGES" | jq '.after' | sed 's/\\n/\
/g')