Skip to content

Add policy evaluation support for cloud backend remote query runs. - #39012

Open
sebasslash wants to merge 2 commits into
mainfrom
tfsearch/cloud-backend-query-plus-policy
Open

Add policy evaluation support for cloud backend remote query runs. #39012
sebasslash wants to merge 2 commits into
mainfrom
tfsearch/cloud-backend-query-plus-policy

Conversation

@sebasslash

Copy link
Copy Markdown
Contributor

This PR updates the cloud backend query operation to correctly forward policy paths to HCP Terraform and improve remote policy summary rendering.

A second client (clientV2) backed by the Kiota-generated go-tfe v2 SDK is now initialised alongside the existing v1 client during Configure() so that PolicyPaths can be forwarded to the HCP Terraform API — the existing go-tfe v1 SDK does not expose this field on QueryRunCreateOptions. The clientV2 is nil-safe: if initialisation fails it is logged as a warning and all existing code paths continue to use the v1 client unchanged.

For Terraform Enterprise installations with a non-default base path (e.g. /tfe/api/v2/), the v2 client is configured with Address = scheme://host and BasePath = /tfe/api/v2 (trailing slash stripped to avoid double-slash in Kiota URL templates).

When op.PolicyPaths is non-empty and clientV2 is non-nil, backend_query.go calls the new createQueryRunV2() helper instead of the v1 QueryRuns.Create, and the v2 response is mapped back into the tfe.QueryRun type the rest of the cloud backend already handles.

The local policy engine client (PolicyClient) is now only initialised when the backend is not a remote backend or the workspace is in local execution mode, since previously it was always started when -policies flags were provided even for remote-execution workspaces where the policy engine runs server-side.

The cloud backend log-streaming loop (renderQueryRunLogs) now handles the policy_query_summary JSON log record type emitted by the remote runner, parsing and rendering it as a human-readable summary via the new exported RenderPolicyQuerySummaryFromJSON helper in internal/command/views, with malformed records skipped gracefully.

The JSON wire field and all internal Go struct fields have been renamed from passed_policies/PassedPolicies to evaluated_policies/EvaluatedPolicies to more accurately reflect that the list contains all policies evaluated against a list block regardless of pass/fail. Result and per-identity labels are now title-cased (Passed, Failed, Error, Unknown) instead of ALLCAPS, the header format changes from Policy results for <addr> (FAIL) to Policy results for <addr> - Failed, and per-identity rows are column-aligned using the width of the longest identity string.

Test Coverage

  • TestCloud_queryWithPolicyPaths — v2 POST path end-to-end with a real HTTP test server
  • TestCloud_queryWithPolicyPaths_Enterprise — correct URL routing for a TFE installation with a non-default base path
  • TestCloud_queryWithNilClientV2 — fallback to v1 when clientV2 is nil
  • TestCloud_queryWithoutPolicyPaths — v1 path taken when no policy paths are provided
  • TestCloud_queryJSONWithPolicySummarypolicy_query_summary records render correctly
  • TestCloud_queryJSONWithPolicySummaryMalformed — malformed records are skipped and the operation still succeeds
  • TestCloud_opQuery_localExecMode_policyPathsWarning — warning emitted when workspace is in local-exec mode and PolicyClient is nil
  • TestCloud_opQuery_localExecMode_policyPathsWithClient — no warning emitted when PolicyClient is already set

Smoke Testing

To verify the end-to-end policy query flow against a live HCP Terraform workspace:

  1. Build the binarygo build -o terraform . && export PATH="$(pwd):$PATH"
  2. Authenticateterraform login (or set TF_TOKEN_app_terraform_io)
  3. Create a workspace with remote execution enabled in your HCP Terraform org
  4. Write a minimal config — a main.tf with at least one resource and a .policy file targeting it
  5. Init and select the workspaceterraform init && terraform workspace select <name>
  6. Run the queryterraform query -policies=./my.policy
  7. Verify v2 path — confirm the request reaches /api/v2/queries (check HCP Terraform audit logs or a local proxy); the policy paths should appear in the request body
  8. Verify summary rendering — the output should display Policy results for <addr> - Passed/Failed with title-cased labels and column-aligned identity rows
  9. Verify fallback — temporarily break clientV2 initialisation (e.g. set an invalid token env var for v2 only) and confirm the command still completes using the v1 path with a [WARN] log line
  10. TFE non-default base path — if you have a TFE instance at a custom path, set TF_CLOUD_HOSTNAME accordingly and confirm no double-slash appears in the request URL

Target Release

1.17.x

Rollback Plan

  • If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.

CHANGELOG entry

  • This change is user-facing and I added a changelog entry.
  • This change is not user-facing.

@sebasslash
sebasslash requested review from a team as code owners August 12, 2026 18:32
@sebasslash
sebasslash force-pushed the tfsearch/cloud-backend-query-plus-policy branch from 9c0920f to 4d83440 Compare August 12, 2026 18:33
@sebasslash sebasslash added the no-changelog-needed Add this to your PR if the change does not require a changelog entry label Aug 12, 2026
When --policies is passed against a cloud backend configured for remote
execution, the command layer was unconditionally starting the local
tfpolicy-plugin binary, surfacing a spurious 'Failed to connect to
policy engine' error even though the operation was about to be
delegated entirely to the remote runner.

Fix 1 (internal/command/query.go): Guard policy client initialisation
with '!isRemoteBackend || b.IsLocalOperations()'. The local client is
still started for plain local backends and for the tfc-agent
(TF_FORCE_LOCAL_BACKEND) path; it is skipped only when the operation
will genuinely run remotely. op.PolicyPaths is always forwarded so the
cloud backend can pass paths to the QueryRunCreate API.

Fix 2 (internal/cloud/backend.go): In Operation(), when the workspace
is in local execution mode, op.PolicyPaths is non-empty, and
op.PolicyClient is nil (because IsLocalOperations() was false when the
command layer ran), emit a 'Policy evaluation skipped' warning via
op.View.Diagnostics before delegating to the local backend.

Tests: two new cases in internal/cloud/backend_query_test.go verify
(a) the warning fires for the local-exec-mode + nil client scenario,
and (b) no warning fires when a policy client is already set.
@sebasslash
sebasslash force-pushed the tfsearch/cloud-backend-query-plus-policy branch from 4d83440 to 2241cf9 Compare August 12, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog-needed Add this to your PR if the change does not require a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant