Skip to content

[SG-4917] feat(terraform_plan): name the resource and attribute in result messages - #286

Closed
refeed wants to merge 2 commits into
mainfrom
feat/result-message-context
Closed

[SG-4917] feat(terraform_plan): name the resource and attribute in result messages#286
refeed wants to merge 2 commits into
mainfrom
feat/result-message-context

Conversation

@refeed

@refeed refeed commented Aug 28, 2026

Copy link
Copy Markdown
Member

Pull Request Template

Description

  • What changes are being made?

    • A result message used to state only the comparison it made. NotEquals printed `"public-read"` is not equal to `"private"` and nothing else — no resource, no attribute — so a check run across a plan with 30 buckets printed 30 identical lines. For the terraform_plan provider, every message now names the resource address, its planned action and the attribute being evaluated:

      Check: no_public_buckets
        FAILED
          1. FAILED: [aws_s3_bucket.example (create)] acl: `"public-read"` is not equal to `"private"`
          2. PASSED: [aws_s3_bucket.logs (update)] acl: `"private"` is equal to `"private"`
          3. FAILED: [aws_vpc.main (create)] attribute: 'flow_log_id' is not found
      
    • Providers may now return an optional context key alongside value / meta / err. core.generate_evaluator_result renders it into the front of the message via a new format_context_prefix() in providers/common.py, and keeps it as structured fields on the result so the SG platform and the TUI don't have to parse the message string:

      {
        "passed": false,
        "message": "[aws_s3_bucket.example (create)] acl: `\"public-read\"` is not equal to `\"private\"`",
        "context": {
          "operation_type": "attribute",
          "resource_type": "aws_s3_bucket",
          "resource_address": "aws_s3_bucket.example",
          "action": "create",
          "attribute": "acl"
        },
        "meta": { "…": "full resource_change" }
      }
    • A wildcard attribute now reports the index it resolved to. ebs_block_device.*.tags.application_acronym used to emit N results per resource that read identically; there was no way to tell which block device was the untagged one. _get_exp_attribute now tracks the traversed path:

      1. FAILED: [aws_instance.example (create)] ebs_block_device.0.tags.application_acronym: `null` is empty
      2. PASSED: [aws_instance.example (create)] ebs_block_device.1.tags.application_acronym: `"TTO"` is not empty
      3. PASSED: [aws_instance.example (create)] ebs_block_device.2.tags.application_acronym: `"TTO"` is not empty
      
    • attribute: '…' is not found now names its resource. With terraform_resource_type: "*" this error was emitted once per resource with identical text and no meta at all.

    • Could not find input value — the one message with no context whatsoever — now names the provider arguments that produced no value.

  • Why are these changes necessary?

    • The detail was already in the result document: the provider attaches the whole resource_change as meta on every output. But nothing between the provider and the pretty printer surfaced it, so the only way to find the offending resource was to pipe --json into another tool.
  • Which issues or tickets does this PR close or relate to?

    • Related issue: SG-4917

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Other (please specify):

Checklist

  • Code has been linted.
  • Documentation has been updated (if needed).
  • Tests have been added or updated (if needed).
  • Any breaking changes have been communicated to the team.

Additional Information

Design — why the context is composed in the core, not in the evaluators. BaseEvaluator.evaluate(evaluator_input, evaluator_data) receives two bare values and nothing else, and all 13 evaluators have exact-dict unit tests. Threading context into each of them would mean rewriting every one of those tests for no gain, so the prefix is composed once in generate_evaluator_result from whatever context the provider chose to attach.

Only terraform_plan populates context here. json, kubernetes, infracost and sg_workflow return outputs without the key, so their messages are unchanged and the context field is omitted from their results entirely. Verified by diffing --json output for all 7 of their fixtures against 1.0.5: byte-for-byte identical. They can adopt the same mechanism later.

Provider-argument errors stay uncontextualised (severity_value: 99, resource_type: '…' is not found) — they are about the policy, not about a resource, so there is nothing to name.

Base branch. Branched from 1.0.5 (c067504), i.e. before 5e690fb (tirith platform check), as requested. main has since touched CHANGELOG.md — that file conflicts (both sides added an [Unreleased] section). Everything else, core.py included, merges cleanly.

Verification

  • pytest: 245 passed. The 11 failures are pre-existing on 1.0.5 and all need a terraform binary on PATH (test_direct_references.py, test_e2e_inline_vars) — the direct_references / direct_dependencies context paths were exercised manually against a synthetic plan instead.
  • black --check src tests clean under black 25.9.0, the version 1.0.5 was clean under. Under the newest black (26.5.1) this branch adds zero complaints beyond the 14 files already flagged on 1.0.5.
  • pydocstyle clean.
  • tests/providers/terraform_plan/test_dot_star_attr.py is untouched and still green — it calls _wrapper_get_exp_attribute directly with 6 parametrized flat-list assertions, which is the regression guard for the traversal refactor. _wrapper_get_exp_attribute keeps its old signature and return value; the path-tracking variant is a sibling.
  • New: tests/providers/terraform_plan/test_message_context.py (9 tests) and format_context_prefix cases in tests/providers/test_common.py. tests/providers/kubernetes/test_attribute.py and tests/providers/json/test_get_value.py gained an assertion each pinning the "other providers unchanged" contract.

A result message only stated the comparison it made, so a check run across a
plan with 30 buckets printed 30 identical lines with no way to tell which
resource had failed. The detail was already in the result document - the
provider attaches the whole resource_change as `meta` on every output - but
nothing between the provider and the pretty printer surfaced it, so reaching it
meant piping --json into another tool.

Providers may now return an optional `context` alongside value/meta/err. The
core renders it into the front of the message and keeps it as structured fields
in the result document, so a failure reads:

    [aws_s3_bucket.example (create)] acl: `"public-read"` is not equal to `"private"`

Only terraform_plan populates it, so every other provider's messages are
unchanged. Two related improvements come with it: a wildcard attribute reports
the index it resolved to (ebs_block_device.0.tags.application_acronym), which
tells apart results that used to read identically, and an "attribute is not
found" error names the resource it is about instead of repeating the same text
once per resource.
@notion-workspace

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.76923% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/tirith/core/core.py 78.94% 2 Missing and 2 partials ⚠️
src/tirith/providers/terraform_plan/handler.py 93.93% 2 Missing ⚠️
Files with missing lines Coverage Δ
src/tirith/providers/common.py 98.48% <100.00%> (+0.33%) ⬆️
src/tirith/providers/terraform_plan/handler.py 65.18% <93.93%> (+2.73%) ⬆️
src/tirith/core/core.py 81.63% <78.94%> (-0.59%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…ing a fixture

fixtures/policy.json has a single `&` in its eval_expression, which is not a
valid operator. Two of these tests only needed a count and an action result, so
they now build a one-evaluator policy for exactly that rather than depending on
a fixture policy that happens to contain one.
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@refeed

refeed commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Superseded-by option: #287 is the same change rebased onto main, so it merges cleanly. This PR stays based on 1.0.5 (before tirith platform check) for a 1.0.x patch line. Merge whichever fits and close the other.

@refeed

refeed commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Not merging this into main — the feat/result-message-context branch will be released as-is from its 1.0.5 base. Closing the PR; the branch stays and is the release source. #287 carries the same change for main.

@refeed refeed closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant