Skip to content

Commit b29bad3

Browse files
iwahbecnunciatoclaude
authored
Blog Post: Compatibility Testing Pulumi HCL (#20876)
* Draft 0 * Add socials * apply suggestion * set as today * Polish compatibility-testing post and wire it into the HCL launch - Convert the Terraform-execution ASCII diagram to a Mermaid flowchart - Replace the GitHub [!NOTE] alert with the {{% notes %}} shortcode - Bump the post date to today so it leads the blog index - Cross-link all four launch posts via related_posts - Add the post to the release page's "From the blog" list - Clear data/blog_home.yaml featured[] to restore date-based ordering - Rename the slug to compatibility-testing-pulumi-hcl to match the title Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Christian Nunciato <chris@nunciato.org> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0fe8c09 commit b29bad3

7 files changed

Lines changed: 161 additions & 5 deletions

File tree

content/blog/bring-your-terraform-estate-into-the-agentic-era/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ schema_type: auto
1616
related_posts:
1717
- terraform-to-pulumi-cloud-hands-on
1818
- terraforms-data-model-on-pulumis-engine
19+
- compatibility-testing-pulumi-hcl
1920

2021
# Social media copy — auto-posted to X, LinkedIn, and Bluesky when merged to master.
2122
# Character limits: X ~280, Bluesky 300, LinkedIn 3000. Leave blank to skip a platform.
@@ -38,7 +39,9 @@ social:
3839
Here's what shipped today.
3940
---
4041

41-
At Pulumi, we are building the platform for agentic infrastructure. Pulumi Cloud provides the guardrails and enterprise readiness needed to safely move fast in this new era. While we are seeing extraordinary adoption — over 40% of our users now manage infrastructure using AI agents — we know many organizations are at different phases in their AI journey and have to balance building for the future with maintaining their existing infrastructure as code (IaC) solutions like Terraform. Today, we are launching three ways that Pulumi lets you avoid trading off building for the future against building for today. You don't have to rip out Terraform to enter the agentic era. Pulumi Cloud brings agentic infrastructure to the IaC estate you already have.
42+
At Pulumi, we are building the platform for agentic infrastructure. Pulumi Cloud provides the guardrails and enterprise readiness needed to safely move fast in this new era. While we are seeing extraordinary adoption — over 40% of our users now manage infrastructure using AI agents — we know many organizations are at different phases in their AI journey and have to balance building for the future with maintaining their existing infrastructure as code (IaC) solutions like Terraform.
43+
44+
Today, [we are launching](/releases/terraform-state-backend-modules-hcl/) three ways that Pulumi lets you avoid trading off building for the future against building for today. You don't have to rip out Terraform to enter the agentic era. Pulumi Cloud brings agentic infrastructure to the IaC estate you already have.
4245

4346
<!--more-->
4447

165 KB
Loading
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: "Compatibility Testing Pulumi HCL"
3+
date: 2026-08-14
4+
draft: false
5+
meta_desc: "How we test that Pulumi HCL is an OpenTofu compatible HCL runtime."
6+
feature_image: feature.png
7+
authors:
8+
- ian-wahbe
9+
tags:
10+
- hcl
11+
- terraform
12+
category: engineering
13+
schema_type: auto
14+
related_posts:
15+
- bring-your-terraform-estate-into-the-agentic-era
16+
- terraform-to-pulumi-cloud-hands-on
17+
- terraforms-data-model-on-pulumis-engine
18+
19+
# Social media copy — auto-posted to X, LinkedIn, and Bluesky when merged to master.
20+
# Character limits: X ~280, Bluesky 300, LinkedIn 3000. Leave blank to skip a platform.
21+
social:
22+
twitter: |
23+
Pulumi HCL has at its core a simple promise: a program that works for tofu apply also works for pulumi up.
24+
25+
That promise makes correctness testable, and it lets LLMs hunt real bugs. Here's how.
26+
linkedin: |
27+
If you squint hard enough, Terraform and Pulumi do the same thing: turn desired state into provider steps. So Pulumi HCL makes a simple promise: a program that works for tofu apply also works for pulumi up.
28+
29+
Our tfcompat tests assert exactly that. A test never says what Pulumi HCL should do — it's just an HCL program and its providers. If OpenTofu and Pulumi HCL don't make the same provider calls, it fails.
30+
31+
Tests like that are very hard to make fail for a bad reason. So we let LLMs write them, and every failure they find is a real divergence.
32+
33+
We wrote up how it works, prompt included.
34+
bluesky: |
35+
Our tfcompat tests never say what Pulumi HCL should do. A test is just an HCL program and its providers; OpenTofu's behavior is the spec.
36+
37+
That one choice lets LLMs hunt our bugs. We wrote up how.
38+
---
39+
40+
[Pulumi HCL](/docs/iac/languages-sdks/hcl/) has at its core a simple promise:
41+
42+
> A program that works for `tofu apply` will also work for `pulumi up`.
43+
44+
This *must* be true to allow Terraform modules to be shared between `tofu` config and Pulumi programs. This property makes testing Pulumi HCL simple. Let me explain.
45+
46+
<!--more-->
47+
48+
At the end of the day, Pulumi is a system to translate actual state & desired state into a series of imperative actions, so actual state can be reconciled to desired state. Terraform is a system to translate actual state & desired state into a series of imperative actions, so actual state can be reconciled to desired state. How desired state is expressed can be radically different, and the underlying reconciliation engine can be radically different, but at the end of the day, both tools do the same thing:
49+
50+
Executing a Terraform program looks like this:
51+
52+
```mermaid
53+
flowchart LR
54+
tf["*.tf files"]
55+
current["current state"]
56+
subgraph engine["reconciliation engine"]
57+
direction LR
58+
desired["desired state"] --> internal["provider steps"]
59+
end
60+
output["provider steps"]
61+
62+
tf --> desired
63+
current --> engine
64+
engine --> output
65+
```
66+
67+
Executing a Pulumi program is more dynamic, because the reconciliation engine is in more active dialog with the user's program. That said, the diagram is the same shape. To [match semantics](/blog/terraforms-data-model-on-pulumis-engine/#providers), Pulumi HCL dynamically bridges [any Terraform provider in the registry](/registry/packages/terraform-provider/). This means that, for the subset of Pulumi programs that are valid OpenTofu programs, both programs take the same input (`*.tf` files) and produce the same step output (Terraform provider steps). Providers are the part of our model that generates user-observable behavior, which means if we match what providers see, we match what users see. This gives us a really nice definition of correctness for Pulumi HCL[^1]:
68+
69+
> Pulumi HCL correctly interprets an HCL program when it generates the same set of provider steps as `tofu` does.
70+
71+
{{% notes type="info" %}}
72+
If you are familiar with property-based testing, you might be thinking this looks like a testable property. You're right.
73+
{{% /notes %}}
74+
75+
[^1]: Pulumi HCL accepts a superset of what OpenTofu accepts. This method only applies to the subset of programs that OpenTofu accepts.
76+
77+
## How we compatibility test Pulumi HCL
78+
79+
We have created a framework to assert on the property above for Pulumi HCL: [`tfcompat`](https://github.com/pulumi/pulumi-hcl/tree/master/tests/tfcompat). Each `tfcompat` test has 2 components:
80+
81+
- The files of the HCL program
82+
- The providers the program uses
83+
84+
I'll walk you through an example test case, then explain how the framework works.
85+
86+
### An example `tfcompat` test
87+
88+
This Go test is the full code of [`TestL2SimpleResource`](https://github.com/pulumi/pulumi-hcl/blob/b966ee6fd0a6d08389856b3d98cb28e58072927d/tests/tfcompat/l2_simple_resource_test.go#L24-L31):
89+
90+
```go
91+
// tests/tfcompat/l2_simple_resource_test.go
92+
93+
func TestL2SimpleResource(t *testing.T) {
94+
t.Parallel()
95+
tfcompat.RunCase(t, "l2_simple_resource", tfcompat.Case{
96+
Providers: []tfcompat.Provider{
97+
{Name: "simple", Factory: providers.SimpleProvider},
98+
},
99+
})
100+
}
101+
```
102+
103+
`Factory` is a function that produces a new in-memory Terraform provider called `"simple"`. The `"l2_simple_resource"` in the test is the folder that contains the actual HCL program under test:
104+
105+
```tf
106+
# tests/tfcompat/testdata/cases/l2_simple_resource/main.tf
107+
108+
resource "simple_resource" "a_resource" {
109+
input_one = "hello"
110+
input_two = true
111+
}
112+
113+
output "some_output" {
114+
value = simple_resource.a_resource.result
115+
}
116+
```
117+
118+
This test asserts that Pulumi HCL & OpenTofu both:
119+
120+
- [ConfigureProvider](https://developer.hashicorp.com/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) the simple provider the same way.
121+
- Call the same [plan](https://developer.hashicorp.com/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc) RPC during `pulumi preview` & `tofu plan`.
122+
- Call the same [ApplyResourceChange](https://developer.hashicorp.com/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) to create the resource.
123+
- Pulumi HCL or OpenTofu didn't call any other provider RPCs.
124+
125+
One really important takeaway is that nowhere in this test case do we write down what Pulumi HCL should do. `tfcompat.RunCase` takes a scenario, but it doesn't take accepted behavior. This will be important later. Before we get there, let me explain how `tfcompat.RunCase` works.
126+
127+
### The anatomy of `tfcompat.RunCase`
128+
129+
Every [`tfcompat.RunCase`](https://github.com/pulumi/pulumi-hcl/blob/b966ee6fd0a6d08389856b3d98cb28e58072927d/tests/testutil/tfcompat/harness.go#L191) runs 2 parallel processes, then compares the results:
130+
131+
- **The Terraform Side:** `tfcompat.RunCase` runs each Terraform provider in-memory, then copies the files in its test directory to a temp dir and runs `tofu plan`, then `tofu apply` against the temp dir. We use [`TF_REATTACH_PROVIDERS`](https://developer.hashicorp.com/terraform/plugin/debugging#running-terraform-with-a-provider-in-debug-mode) to have `tofu` attach to our in-memory Terraform providers.
132+
133+
- **The Pulumi Side:** `tfcompat.RunCase` runs each Terraform provider in-memory & copies the test files in its test directory to a separate test dir, and runs `pulumi preview`, then `pulumi up` against the temp dir. We use [`PULUMI_BRIDGE_REATTACH_PROVIDERS`](https://github.com/pulumi/pulumi-terraform-bridge/pull/3559) to instruct [our dynamic bridge](/registry/packages/terraform-provider/) to attach to our in-memory provider.
134+
135+
**For both** Pulumi & Terraform, the test harness [records each provider's gRPC calls](https://github.com/pulumi/pulumi-hcl/blob/b966ee6fd0a6d08389856b3d98cb28e58072927d/tests/testutil/tfexec/recorder.go#L50) for all providers and it records the stack outputs for both invocations.
136+
137+
After both runs have completed, the test asserts that the outputs of the Pulumi program & the Terraform program match, and that the providers saw the same operations. A test case passes if and only if the providers for OpenTofu & Pulumi saw the same operations, and stack outputs were equal.
138+
139+
## Writing tests with LLMs
140+
141+
Because tfcompat tests assert that Pulumi HCL matches OpenTofu, and not the test author's idea of correctness, we can use LLMs to effectively hunt for bugs. Without additional constraints, telling Claude or Codex to find a bug will produce mostly false positives. Because our tests need only a scenario to test, it is very hard[^2] for LLMs to produce false positives. This allows useful bug finding runs with as simple a prompt as:
142+
143+
> I'd like you to do a pass trying to find bugs. You will prove each bug with a genuine failing tfcompat test. Start with 10 sub-agents. Bugs should not be duplicates and bugs should not reflect existing issues. Each failure should be stood up as a draft PR with just the failing test added. These PRs will fail CI. That is intentional. Don't try to fix the bugs you solved. Keep the sub-agents running until you have found 10 failures. You are responsible for validating that the bugs are real and ensuring that the sub-agents do not create duplicate bugs, so you should create the PRs directly.
144+
145+
This is supported by [a](https://github.com/pulumi/pulumi-hcl/blob/571fedb720fe7cd7d8f37ce01990c7c2df384658/.claude/skills/find-tfcompat-bug/SKILL.md) [couple](https://github.com/pulumi/pulumi-hcl/blob/571fedb720fe7cd7d8f37ce01990c7c2df384658/.claude/skills/swarm-tfcompat-bugs/SKILL.md) [of](https://github.com/pulumi/pulumi-hcl/blob/2709fcb5d5825f69d1213c9d176f40d6bc52c98e/.claude/skills/farm-tfcompat-bugs/SKILL.md) [skills](https://github.com/pulumi/pulumi-hcl/blob/571fedb720fe7cd7d8f37ce01990c7c2df384658/.claude/skills/fix-tfcompat-bug/SKILL.md), but this strategy does [genuinely](https://github.com/pulumi/pulumi-hcl/pull/447) [find](https://github.com/pulumi/pulumi-hcl/pull/397) [high-quality](https://github.com/pulumi/pulumi-hcl/pull/411) [bugs](https://github.com/pulumi/pulumi-hcl/pull/422).
146+
147+
Because of how easy it is to send LLMs to hunt bugs, I think of this almost as a property-based test with LLMs as both the case generator and the reducer.
148+
149+
[^2]: Typically, false positives come from an LLM finding a scenario where `tofu apply` errors, but `pulumi up` behaves correctly.
150+
151+
## Conclusion
152+
153+
Having a strong and testable definition for Pulumi HCL makes it easy & fast to write integration tests, ensuring that our implementation is correct. LLMs are excellent at finding bugs when given the ability to write tests that fail if and only if they show a real divergence between our HCL implementation & OpenTofu, letting us hunt for bugs at LLM scale. All this testing has made us pretty confident that [what we've shipped](/releases/terraform-state-backend-modules-hcl/) is pretty close to full parity with OpenTofu, and we'd love it if you [gave it a try](/docs/iac/languages-sdks/hcl/).

content/blog/terraform-to-pulumi-cloud-hands-on/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ category: tutorials
1616
related_posts:
1717
- bring-your-terraform-estate-into-the-agentic-era
1818
- terraforms-data-model-on-pulumis-engine
19+
- compatibility-testing-pulumi-hcl
1920
---
2021

2122
[Today's big release](/releases/terraform-state-backend-modules-hcl/) contains a whole new set of features designed for seamless interoperability with the Terraform and OpenTofu ecosystems, and there's a lot there — so much that it can be tough to get your head around all of it. But it generally falls into three major categories:

content/blog/terraforms-data-model-on-pulumis-engine/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ schema_type: auto
1414
related_posts:
1515
- bring-your-terraform-estate-into-the-agentic-era
1616
- terraform-to-pulumi-cloud-hands-on
17+
- compatibility-testing-pulumi-hcl
1718

1819
# Social media copy — auto-posted to X, LinkedIn, and Bluesky when merged to master.
1920
# Character limits: X ~280, Bluesky 300, LinkedIn 3000. Leave blank to skip a platform.

content/releases/terraform-state-backend-modules-hcl.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,5 @@ blog_section:
149149
- /blog/bring-your-terraform-estate-into-the-agentic-era
150150
- /blog/terraform-to-pulumi-cloud-hands-on
151151
- /blog/terraforms-data-model-on-pulumis-engine
152+
- /blog/compatibility-testing-pulumi-hcl
152153
---

data/blog_home.yaml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
# featured: [my-big-launch] # pin to slot 1 (hero); 2–4 auto-fill
1616
# featured: [auto, my-editors-pick] # slot 1 = latest post, slot 2 = pinned
1717
# featured: [auto, pick-a, auto, pick-b] # slots 1 & 3 latest; slots 2 & 4 pinned
18-
featured:
19-
- bring-your-terraform-estate-into-the-agentic-era
20-
- terraforms-data-model-on-pulumis-engine
21-
- terraform-to-pulumi-cloud-hands-on
18+
featured: []
2219

2320
# Optional list of up to 4 series slugs (from data/blog_series.yml) for the
2421
# "Popular series" strip. Defaults to the first 4 entries of blog_series.yml,

0 commit comments

Comments
 (0)