Skip to content

feat: Add Terraform integration#3290

Open
smithjilks wants to merge 28 commits intosuperplanehq:mainfrom
smithjilks:feat/add-terraform-integration
Open

feat: Add Terraform integration#3290
smithjilks wants to merge 28 commits intosuperplanehq:mainfrom
smithjilks:feat/add-terraform-integration

Conversation

@smithjilks
Copy link

@smithjilks smithjilks commented Feb 26, 2026

Implements #2348

What changed

Adds a Terraform (HCP Terraform / Terraform Enterprise) integration with the following components:

Triggers:

  • On Run Event — listens to run state transitions (created, planning, applying, completed, errored, drift detected, etc.)

Actions:

  • Run Plan — queues a new run for a workspace

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

  • Fire when Terraform Cloud sends a notification to the configured webhook
  • Optional event type filter to scope the trigger to specific run states
  • Emits a terraform.runEvent or terraform.needsAttention event with run details (runId, status, workspace, URL, etc.)

Action — Run Plan

  • Creates a seculative plan on Terraform.

Integration setup

  • Authenticates via a Terraform Cloud Team API token configured by the user
  • Automatically creates a signed webhook in Terraform Cloud on canvas save — no manual setup required
  • Verifies incoming webhook requests using HMAC-SHA512 signatures (X-TFE-Notification-Signature header)
  • Automatically deletes the webhook from Terraform Cloud when the trigger is removed

Demo

Screencast.From.2026-03-03.13-59-54.mp4

@smithjilks smithjilks force-pushed the feat/add-terraform-integration branch from d3909eb to 5fe2824 Compare February 27, 2026 09:44
@AleksandarCole AleksandarCole added pr:stage-2/3 Needs to pass functional review labels Feb 27, 2026
@@ -0,0 +1,378 @@
package terraform
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each component needs to be in its own file

"fmt"
"strings"

"github.com/hashicorp/go-tfe"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't import the SDK

WebhookSecret string `json:"webhookSecret"`
}

func getConfigurationFields() []configuration.Field {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why put this in a separate file and function?

ActionTimeout = "timeout"
)

type WaitForApproval struct{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@smithjilks smithjilks force-pushed the feat/add-terraform-integration branch from a3892fa to 93ca6bf Compare March 2, 2026 08:30
@smithjilks smithjilks force-pushed the feat/add-terraform-integration branch from 4f02151 to 164a05b Compare March 2, 2026 10:10
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>
@smithjilks smithjilks force-pushed the feat/add-terraform-integration branch from 89f1849 to 06544ce Compare March 3, 2026 08:12
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

return fmt.Errorf("failed to override policy: bad status %d", resp.StatusCode)
}
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Signed-off-by: Jilks Smith <smithjilks@gmail.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 plannedcost_estimating → … → policy_checkingpolicy_checked/policy_soft_failedplanned_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.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ken pr:stage-2/3 Needs to pass functional review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants