Skip to content

feat(lint): Lint TF with tflint#828

Open
stevendee-recon wants to merge 1 commit into
aspect-build:mainfrom
stevendee-recon:tflint
Open

feat(lint): Lint TF with tflint#828
stevendee-recon wants to merge 1 commit into
aspect-build:mainfrom
stevendee-recon:tflint

Conversation

@stevendee-recon

@stevendee-recon stevendee-recon commented Apr 2, 2026

Copy link
Copy Markdown

This enables linting Terraform projects with tflint. The approach taken here is agnostic to the specific TF (Terraform / OpenTofu) ruleset used; plain filegroups work, as does anything that generates tf_module and/or tf_environment rule kinds (or any configurable rule kind.)

tflint plugins are supported via a repository rule that vendors them at specific hashes to account for terraform-linters/tflint#1634.

Lint runs are done hermetically out of a staging directory, with sources and plugins symlinked in as needed.

I have left the Starzelle plugin and HCL tree-sitter work out of this PR, to be done elsewhere.

This code was done heavily AI-assisted, but I have attempted to review it and comprehend it, and am using it elsewhere.

Part of #325.


Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below:

Add support for linting TF (Terraform/OpenTofu) modules

Test plan

  • New test cases added: lint output on sample TF files is checked from tflint and an external plugin

@CLAassistant

CLAassistant commented Apr 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@aspect-workflows

aspect-workflows Bot commented Apr 2, 2026

Copy link
Copy Markdown

Bazel 7 (Test)

All tests were cache hits

6 tests (100.0%) were fully cached saving 1s.


Bazel 8 (Test)

All tests were cache hits

6 tests (100.0%) were fully cached saving 1s.


Bazel 9 (Test)

All tests were cache hits

6 tests (100.0%) were fully cached saving 1s.

This enables linting Terraform projects with [tflint]. The approach
taken here is agnostic to the specific TF (Terraform / OpenTofu) ruleset
used; plain filegroups work, as does anything that generates `tf_module`
and/or `tf_environment` rule kinds (or any configurable rule kind.)

`tflint` plugins are supported via a repository rule that vendors them
at specific hashes to account for terraform-linters/tflint#1634.

Lint runs are done hermetically out of a staging directory, with sources
and plugins symlinked in as needed.

I have left the Starzelle plugin and HCL tree-sitter work out of this
PR, to be done elsewhere.

This code was done heavily AI-assisted, but I have attempted to review
it and comprehend it, and am using it elsewhere.

Part of aspect-build#325.

[tflint]: https://github.com/terraform-linters/tflint

Co-authored-by: Claude <noreply@anthropic.com>
@jbedard

jbedard commented Apr 5, 2026

Copy link
Copy Markdown
Member

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3fd0ac36b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lint/tflint.bzl
Comment on lines +267 to +269
"_plugins": attr.label_list(
default = plugins,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve tflint plugins in exec configuration

The _plugins attribute is declared without cfg = "exec", so plugin labels are analyzed in the target configuration even though their binaries are loaded at execution time by tflint. With cross-platform builds/executors, this can select the wrong plugin artifact from @tflint_plugin_*//:plugin and cause plugin load failures (for example exec format error) when the lint action runs.

Useful? React with 👍 / 👎.

Comment thread lint/tflint.bzl
Comment on lines +84 to +88
copy_cmds = ["ln -s $PWD/{src} {dir}/{basename}".format(
src = src.path,
dir = staging_dir,
basename = src.basename,
) for src in srcs]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve source subpaths when staging Terraform files

Each Terraform source is symlinked into the staging directory by src.basename only, which drops directory structure. If a target includes files from different directories with the same filename (for example module_a/main.tf and module_b/main.tf), one symlink conflicts and that file is not staged, so lint coverage becomes incomplete or incorrect for tagged filegroups/multi-module targets.

Useful? React with 👍 / 👎.

Comment thread lint/tflint.bzl

# Determine the common package prefix for the source files so we can
# rewrite tflint's output paths back to workspace-relative locations.
src_prefix = srcs[0].short_path.rsplit("/", 1)[0] + "/" if srcs and "/" in srcs[0].short_path else ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you copy this pattern from somewhere? Isn't it possible that srcs[0] doesn't have the "common package prefix" and srcs[1] has something else?

Comment thread lint/tflint.bzl
# Symlink .tf sources into the scratch directory (flat — tflint lints one
# module directory at a time). Absolute paths ensure symlinks resolve after
# --chdir into the staging directory.
copy_cmds = ["ln -s $PWD/{src} {dir}/{basename}".format(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the heavy use of symlinks and creating the .tflint.d/ really the best way of doing this? Why doesn't this directory already exist in the correct format? Why do we have to create it only at lint-time?

@stevendee-recon

Copy link
Copy Markdown
Author

Thank you for the review.

Did you copy this pattern from somewhere?

Wish I could tell you. Claude came up with it; I was insufficiently skeptical.

FWIW I think what broke in my mind, as reviewer, was something like "aspect lint must not respect Bazel's sandboxing so extra effort is needed to make a lint run hermetic."

Will take a pass, removing the unnecessary copies and staging the plugin repository rule appropriately.

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.

3 participants