feat: Add Terraform integration#3290
Conversation
d3909eb to
5fe2824
Compare
| @@ -0,0 +1,378 @@ | |||
| package terraform | |||
There was a problem hiding this comment.
Each component needs to be in its own file
pkg/integrations/terraform/client.go
Outdated
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/hashicorp/go-tfe" |
There was a problem hiding this comment.
We shouldn't import the SDK
| WebhookSecret string `json:"webhookSecret"` | ||
| } | ||
|
|
||
| func getConfigurationFields() []configuration.Field { |
There was a problem hiding this comment.
Why put this in a separate file and function?
| ActionTimeout = "timeout" | ||
| ) | ||
|
|
||
| type WaitForApproval struct{} |
There was a problem hiding this comment.
We already have an approval core component, which allow you to select users / roles / groups to approve. I don't think a separate one just for controlling terraform runs is required.
a3892fa to
93ca6bf
Compare
4f02151 to
164a05b
Compare
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
…and policy_checked states Signed-off-by: Jilks Smith <smithjilks@gmail.com>
…k payloads Signed-off-by: Jilks Smith <smithjilks@gmail.com>
…onfiguration Signed-off-by: Jilks Smith <smithjilks@gmail.com>
…onents Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
…ompact components Signed-off-by: Jilks Smith <smithjilks@gmail.com>
89f1849 to
06544ce
Compare
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
| getState: terraformStateFunction, | ||
| }; | ||
|
|
||
| // removed terraformDataBuilder |
There was a problem hiding this comment.
Accidentally committed development note in component mapper
Low Severity
The comment // removed terraformDataBuilder is a leftover development note. There is no terraformDataBuilder anywhere else in the codebase — this was a personal reminder during development that wasn't cleaned up before submitting the PR.
| return fmt.Errorf("failed to override policy: bad status %d", resp.StatusCode) | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Unused exported client methods add dead code
Low Severity
The exported methods ApplyRun, DiscardRun, OverridePolicy, ListPolicyChecks, and the PolicyChecksPayload type are defined but never called anywhere in the codebase. The PR reviewer explicitly asked to "just do a simple 'Run plan' component first," and only CreateRun, ReadRun, and CancelRun are actually used.
Additional Locations (1)
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
| } | ||
|
|
||
| if createErr != nil { | ||
| return nil, fmt.Errorf("failed to create terraform webhook after %d attempts: %w", maxRetries, createErr) |
There was a problem hiding this comment.
Retry error message reports wrong attempt count
Low Severity
The error message at the end of the retry loop always reports maxRetries (3) as the number of attempts, but the loop can exit early — for example, on a 4xx client error (not 429) it breaks after just one attempt, or if newRequest fails it also breaks immediately. The resulting error message "failed to create terraform webhook after 3 attempts" would be inaccurate, potentially confusing during debugging.
Additional Locations (1)
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| return true | ||
| } | ||
| return false | ||
| } |
There was a problem hiding this comment.
Terminal state "planned" causes premature poll termination
Medium Severity
Including "planned" in isTerminalStatePlanTarget causes polling to stop at an intermediate state. For plan-only runs on workspaces with cost estimation or Sentinel policies, the run progresses planned → cost_estimating → … → policy_checking → policy_checked/policy_soft_failed → planned_and_finished. By treating "planned" as terminal, the poll exits before those phases run, emitting on the "passed" channel. If a Sentinel policy later soft-fails, the workflow never sees the policy_soft_failed state — even though isFailedState was written to catch it. This makes the downstream cost_estimated, policy_checked, and policy_soft_failed cases in isTerminalStatePlanTarget effectively unreachable for most runs.


Implements #2348
What changed
Adds a Terraform (HCP Terraform / Terraform Enterprise) integration with the following components:
Triggers:
Actions:
Why
Enables SuperPlane workflows to orchestrate Terraform Cloud infrastructure changes — for example, requiring manual approval before applying production changes, tracking run progress, or sending Slack notifications when runs fail or drift is detected.
How
Triggers — On Run Event
Action — Run Plan
Integration setup
Demo
Screencast.From.2026-03-03.13-59-54.mp4