Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bazel_dep(name = "rules_proto", version = "6.0.0")

# Needed in the root because we dereference the toolchain in our aspect impl
bazel_dep(name = "rules_buf", version = "0.5.2")
bazel_dep(name = "rules_tf", version = "0.0.10")

tel = use_extension("@aspect_tools_telemetry//:extension.bzl", "telemetry")
use_repo(tel, "aspect_tools_telemetry_report")
Expand All @@ -41,6 +42,17 @@ use_repo(rules_lint_toolchains, "sarif_parser_toolchains")

register_toolchains("@sarif_parser_toolchains//:all")

tf_repositories = use_extension("@rules_tf//tf:extensions.bzl", "tf_repositories")
tf_repositories.download(
mirror = {
"aws": "hashicorp/aws:5.90.0",
},
version = "1.9.8",
)
use_repo(tf_repositories, "tf_toolchains")

register_toolchains("@tf_toolchains//:all")

####### Dev dependencies ########

bazel_dep(name = "bazelrc-preset.bzl", version = "1.1.0", dev_dependency = True)
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Linters which are not language-specific:
| Go | [gofmt] or [gofumpt] | |
| Gherkin | [prettier-plugin-gherkin] | |
| GraphQL | [Prettier] | |
| HCL (Hashicorp Config) | [terraform] fmt | |
| HCL / Terraform | [terraform] fmt | [TFLint] |
| HTML | [Prettier] | |
| JSON | [Prettier] | |
| Java | [google-java-format] | [pmd] , [Checkstyle], [Spotbugs] |
Expand All @@ -60,12 +60,18 @@ Linters which are not language-specific:
| YAML | [yamlfmt] | [yamllint] |
| XML | [prettier/plugin-xml] | |

Terraform tooling uses the binaries published by [`rules_tf`](https://github.com/yanndegat/rules_tf)
when bzlmod is enabled. See [docs/formatting.md](./docs/formatting.md#terraform-formatter) and
[docs/linting.md](./docs/linting.md#terraform-linting) for setup details, including guidance for
WORKSPACE users via `rules_lint_setup_tf_toolchains`.

[prettier]: https://prettier.io
[google-java-format]: https://github.com/google/google-java-format
[flake8]: https://flake8.pycqa.org/en/latest/index.html
[pmd]: https://docs.pmd-code.org/latest/index.html
[checkstyle]: https://checkstyle.sourceforge.io/cmdline.html
[spotbugs]: https://spotbugs.github.io/
[TFLint]: https://github.com/terraform-linters/tflint
[buf lint]: https://buf.build/docs/lint/overview
[eslint]: https://eslint.org/
[swiftformat]: https://github.com/nicklockwood/SwiftFormat
Expand Down
34 changes: 34 additions & 0 deletions docs/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,40 @@ format_multirun(

File discovery for each language is based on file extension and shebang-based discovery is currently limited to shell.

### Terraform formatter

When formatting Terraform sources we recommend using the binary published with
[`rules_tf`](https://github.com/yanndegat/rules_tf) so the version is managed by Bazel.
Under bzlmod you can add the module and register the toolchains so the built-in `terraform`
language attribute resolves to the correct executable:

```starlark
bazel_dep(name = "rules_tf", version = "0.0.10")

tf = use_extension("@rules_tf//tf:extensions.bzl", "tf_repositories")
tf.download(
version = "1.9.8",
mirror = {"aws": "hashicorp/aws:5.90.0"},
)
use_repo(tf, "tf_toolchains")
register_toolchains("@tf_toolchains//:all")
```

WORKSPACE users can continue to use the multitool-provided binary without any additional setup.
If you need the same version pin in WORKSPACE mode, download `rules_tf` via
`rules_lint_dependencies()` and register the toolchains with the helper provided in this repo:

```starlark
load("@aspect_rules_lint//lint:tf_toolchains_workspace.bzl", "rules_lint_setup_tf_toolchains")

rules_lint_setup_tf_toolchains(
version = "1.9.8",
mirror = {"aws": "hashicorp/aws:5.90.0"},
)
```

The helper detects the host platform automatically using Bazel's host platform constraints.

## Usage

### Configuring formatters
Expand Down
40 changes: 40 additions & 0 deletions docs/linting.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ Each linter aspect accepts the configuration file(s) as an argument.
To specify whether a certain lint rule should be a warning or error, follow the documentation for the linter.
rules_lint provides the exit code of the linter process to allow the desired developer experiences listed above.

### Terraform linting

Terraform modules can be linted with `lint_tflint_aspect`, which relies on the toolchains published by
[`rules_tf`](https://github.com/yanndegat/rules_tf). Add that module (or the equivalent WORKSPACE repositories)
and register the toolchains so the aspect can locate both Terraform and TFLint binaries:

```starlark
bazel_dep(name = "rules_tf", version = "0.0.10")

tf = use_extension("@rules_tf//tf:extensions.bzl", "tf_repositories")
tf.download(
version = "1.9.8",
mirror = {"aws": "hashicorp/aws:5.90.0"},
)
use_repo(tf, "tf_toolchains")
register_toolchains("@tf_toolchains//:all")
```

Then declare the aspect alongside your other linters:

```starlark
load("@aspect_rules_lint//lint:tflint.bzl", "lint_tflint_aspect")

tflint = lint_tflint_aspect()
```

In WORKSPACE projects, fetch `rules_tf` via `rules_lint_dependencies()` and register matching
toolchains using the helper exposed by rules_lint:

```starlark
load("@aspect_rules_lint//lint:tf_toolchains_workspace.bzl", "rules_lint_setup_tf_toolchains")

rules_lint_setup_tf_toolchains(
version = "1.9.8",
mirror = {"aws": "hashicorp/aws:5.90.0"},
)
```

The helper detects the host platform automatically using Bazel's host platform constraints.

## Ignoring targets

To ignore a specific target, you can use the `no-lint` tag. This will prevent the linter from visiting the target.
Expand Down
1 change: 1 addition & 0 deletions example/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bazel_dep(name = "rules_cc", version = "0.0.9")
bazel_dep(name = "gazelle", version = "0.41.0")
bazel_dep(name = "rules_shell", version = "0.5.0")
bazel_dep(name = "jq.bzl", version = "0.4.0")
bazel_dep(name = "rules_tf", version = "0.0.10")

local_path_override(
module_name = "aspect_rules_lint",
Expand Down
14 changes: 14 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
The `src/` folder contains a project that we want to lint.
It contains sources in multiple languages.

The `terraform/` directory demonstrates formatting and linting Terraform modules via
[`rules_tf`](https://github.com/yanndegat/rules_tf). The providers and toolchains are configured in
`MODULE.bazel`, so the examples work out-of-the-box with `bazel run` and `bazel build`.

### With Aspect CLI

Run `bazel lint src:all`
Expand Down Expand Up @@ -33,6 +37,16 @@ From /shared/cache/bazel/user_base/b6913b1339fd4037a680edabc6135c1d/execroot/_ma

```

Terraform modules can be linted and formatted the same way. For example:

```
# Lint the sample module with TFLint
bazel build //terraform/aws_subnet:module --aspects //tools/lint:linters.bzl%tflint --output_groups=rules_lint_human

# Apply formatting to Terraform sources
bazel run //:format -- terraform/aws_subnet/main.tf
```

## ESLint

This folder simply follows the instructions at https://typescript-eslint.io/getting-started
Expand Down
14 changes: 14 additions & 0 deletions example/WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ http_archive(
url = "https://github.com/bazelbuild/rules_shell/releases/download/v0.5.0/rules_shell-v0.5.0.tar.gz",
)

http_archive(
name = "rules_tf",
sha256 = "64e5f0705d7b434e6942445ea1c3259a7b0ccc70f14827e857662aae0443274e",
strip_prefix = "rules_tf-0.0.10",
url = "https://github.com/yanndegat/rules_tf/archive/refs/tags/v0.0.10.tar.gz",
)

http_archive(
name = "bazel_features",
sha256 = "06f02b97b6badb3227df2141a4b4622272cdcd2951526f40a888ab5f43897f14",
Expand Down Expand Up @@ -140,6 +147,13 @@ load("@npm//:repositories.bzl", "npm_repositories")

npm_repositories()

load("@aspect_rules_lint//lint:tf_toolchains_workspace.bzl", "rules_lint_setup_tf_toolchains")

rules_lint_setup_tf_toolchains(
version = "1.9.8",
mirror = {"aws": "hashicorp/aws:5.90.0"},
)

http_archive(
name = "aspect_rules_ts",
sha256 = "ee7dcc35faef98f3050df9cf26f2a72ef356cab8ad927efb1c4dc119ac082a19",
Expand Down
2 changes: 1 addition & 1 deletion example/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if [ $machine == "Windows" ]; then
# avoid missing linters on windows platform
args=("--aspects=$(echo //tools/lint:linters.bzl%{flake8,pylint,pmd,ruff,vale,yamllint,clang_tidy} | tr ' ' ',')")
else
args=("--aspects=$(echo //tools/lint:linters.bzl%{buf,eslint,flake8,keep_sorted,ktlint,pmd,pylint,ruff,shellcheck,stylelint,vale,yamllint,clang_tidy,spotbugs} | tr ' ' ',')")
args=("--aspects=$(echo //tools/lint:linters.bzl%{buf,eslint,flake8,keep_sorted,ktlint,pmd,pylint,ruff,shellcheck,stylelint,tflint,vale,yamllint,clang_tidy,spotbugs} | tr ' ' ',')")
fi

# NB: perhaps --remote_download_toplevel is needed as well with remote execution?
Expand Down
11 changes: 11 additions & 0 deletions example/terraform/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("@rules_tf//tf:def.bzl", "tf_providers_versions")

package(default_visibility = ["//visibility:public"])

tf_providers_versions(
name = "providers",
tf_version = "1.9.8",
providers = {
"aws": "hashicorp/aws:5.90.0",
},
)
9 changes: 9 additions & 0 deletions example/terraform/aws_subnet/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("@rules_tf//tf:def.bzl", "tf_module")

package(default_visibility = ["//visibility:public"])

tf_module(
name = "aws_subnet_module",
providers = ["aws"],
providers_versions = "//terraform:providers",
)
9 changes: 9 additions & 0 deletions example/terraform/aws_subnet/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "aws_subnet" "example" {
vpc_id = var.vpc_id
cidr_block = "10.0.0.0/24"
availability_zone = "us-east-2a"

tags={
Name = "aspect-rules-lint-example"
}
}
3 changes: 3 additions & 0 deletions example/terraform/aws_subnet/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "subnet_id" {
value = aws_subnet.example.id
}
3 changes: 3 additions & 0 deletions example/terraform/aws_subnet/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "vpc_id" {
type = string
}
10 changes: 10 additions & 0 deletions example/terraform/aws_subnet/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = "~> 1.9.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "5.90.0"
}
}
}
9 changes: 8 additions & 1 deletion example/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("@rules_python//python:defs.bzl", "py_library")
load("@rules_shell//shell:sh_library.bzl", "sh_library")
load("//tools/lint:linters.bzl", "checkstyle_test", "eslint_test", "flake8_test", "pmd_test", "pylint_test", "ruff_test", "shellcheck_test")
load("//tools/lint:linters.bzl", "checkstyle_test", "eslint_test", "flake8_test", "pmd_test", "pylint_test", "ruff_test", "shellcheck_test", "tflint_test")

write_file(
name = "ts_code_generator",
Expand Down Expand Up @@ -171,6 +171,13 @@ shellcheck_test(
tags = ["manual"],
)

tflint_test(
name = "tflint",
srcs = ["//terraform/aws_subnet:module"],
# Expected to fail based on current content of the module.
tags = ["manual"],
)

format_test(
name = "format_test_manual",
srcs = [":generated_sh"],
Expand Down
22 changes: 22 additions & 0 deletions example/test/lint_test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ src/hello.css
EOF
}

function assert_terraform_lints() {
# TFLint
echo <<"EOF" | assert_output --partial
Notice: `subnet_id` output has no description (terraform_documented_outputs)

on terraform/aws_subnet/outputs.tf line 1:
1: output "subnet_id" {
EOF
echo <<"EOF" | assert_output --partial
Notice: `vpc_id` variable has no description (terraform_documented_variables)

on terraform/aws_subnet/variables.tf line 1:
1: variable "vpc_id" {
EOF
}

@test "should produce reports" {
run $BATS_TEST_DIRNAME/../lint.sh //src:all --no@aspect_rules_lint//lint:color
assert_success
Expand Down Expand Up @@ -116,3 +132,9 @@ EOF
# This lint check is disabled in the .eslintrc.cjs file
refute_output --partial "Unexpected 'debugger' statement"
}

@test "should report terraform issues from tflint" {
run $BATS_TEST_DIRNAME/../lint.sh //terraform/aws_subnet:module --no@aspect_rules_lint//lint:color
assert_success
assert_terraform_lints
}
13 changes: 13 additions & 0 deletions example/test/machine_outputs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ load(
"machine_ruff_report",
"machine_shellcheck_report",
"machine_stylelint_report",
"machine_tflint_report",
"machine_vale_report",
"machine_yamllint_report",
"report_test",
Expand Down Expand Up @@ -134,6 +135,18 @@ report_test(
report = "machine_buf_report",
)

machine_tflint_report(
name = "machine_tflint_report",
src = "//terraform/aws_subnet:module",
)

report_test(
name = "tflint_machine_output_test",
expected_tool = "tflint",
expected_uri = "terraform/aws_subnet/variables.tf",
report = "machine_tflint_report",
)

# FIXME: I'm getting a C++ compile failure on macos
# machine_clang_tidy_report(
# name = "machine_clang_tidy_report",
Expand Down
9 changes: 7 additions & 2 deletions example/test/machine_outputs/machine_output.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

load("@bazel_features//:features.bzl", "bazel_features")
load("@jq.bzl//jq:jq.bzl", "jq_test")
load("//tools/lint:linters.bzl", "buf", "clang_tidy", "eslint", "flake8", "pylint", "ruff", "shellcheck", "stylelint", "vale", "yamllint")
load("//tools/lint:linters.bzl", "buf", "clang_tidy", "eslint", "flake8", "pylint", "ruff", "shellcheck", "stylelint", "tflint", "vale", "yamllint")

SARIF_TOOL_DRIVER_NAME_FILTER = ".runs[].tool.driver.name"
SARIF_TOOL_DRIVER_NAME_FILTER = ".runs[0].tool.driver.name"
PHYSICAL_ARTIFACT_LOCATION_URI_FILTER = ".runs[].results | map(.locations | map(.physicalLocation.artifactLocation.uri)) | flatten | unique[]"

def report_test(name, report, expected_tool, expected_uri):
Expand Down Expand Up @@ -79,3 +79,8 @@ machine_yamllint_report = rule(
implementation = _machine_report,
attrs = {"src": attr.label(aspects = [yamllint])},
)

machine_tflint_report = rule(
implementation = _machine_report,
attrs = {"src": attr.label(aspects = [tflint])},
)
5 changes: 5 additions & 0 deletions example/tools/lint/linters.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ load("@aspect_rules_lint//lint:pylint.bzl", "lint_pylint_aspect")
load("@aspect_rules_lint//lint:shellcheck.bzl", "lint_shellcheck_aspect")
load("@aspect_rules_lint//lint:spotbugs.bzl", "lint_spotbugs_aspect")
load("@aspect_rules_lint//lint:stylelint.bzl", "lint_stylelint_aspect")
load("@aspect_rules_lint//lint:tflint.bzl", "lint_tflint_aspect")
load("@aspect_rules_lint//lint:vale.bzl", "lint_vale_aspect")
load("@aspect_rules_lint//lint:yamllint.bzl", "lint_yamllint_aspect")

Expand Down Expand Up @@ -107,6 +108,10 @@ ktlint = lint_ktlint_aspect(

ktlint_test = lint_test(aspect = ktlint)

tflint = lint_tflint_aspect()

tflint_test = lint_test(aspect = tflint)

clang_tidy = lint_clang_tidy_aspect(
binary = Label("//tools/lint:clang_tidy"),
configs = [
Expand Down
Loading
Loading