Skip to content
DevOps AI ToolKit
Newsletter
All prompts
AI for Terraform Difficulty: Advanced ClaudeChatGPTCursor

Terraform TFLint Custom Ruleset Authoring Prompt

Author a custom TFLint ruleset — .tflint.hcl config, the terraform + aws bundled rulesets, and Go rules built on the TFLint plugin SDK to enforce naming conventions and required tags, then wire it into pre-commit and CI.

Target user
Platform teams enforcing org-specific Terraform standards
Difficulty
Advanced
Tools
Claude, ChatGPT, Cursor

The prompt

You are a Terraform tooling engineer who has shipped a real TFLint plugin in Go and knows the difference between a bundled ruleset and a custom plugin.

I will provide:
- The conventions I want enforced (naming patterns, mandatory tags, banned resources/regions)
- Provider(s) in use and TFLint version
- Where linting runs today (local, pre-commit, CI) and how strict gating is

Your job:

1. **Baseline config** — write a `.tflint.hcl` that enables the `terraform` bundled ruleset (`preset = "recommended"`) and the `aws` plugin pinned by `version` + `source`, and set `plugin` blocks correctly. Explain `tflint --init` fetching plugins.

2. **Decide: config rule vs custom plugin** — many needs (naming, deep-checking) are covered by the bundled `terraform` ruleset rules like `terraform_naming_convention` configured with a `format`/`custom` regex. Only reach for a Go plugin when no built-in rule fits. Tell me which of MY rules need code vs config.

3. **Custom Go rule** — for a genuinely custom check (e.g. "every taggable resource must carry a `cost-center` tag"), write a rule against the TFLint plugin SDK: a struct implementing `tflint.Rule` (`Name`, `Enabled`, `Severity`, `Check(runner tflint.Runner)`), using `runner.GetResourceContent` / `runner.WalkExpressions` to inspect attributes and `runner.EmitIssue` to report. Include the `main.go` that registers the ruleset via `plugin.Serve`.

4. **Build + distribute** — how to `go build` the plugin, name it `tflint-ruleset-<name>`, and reference it from `.tflint.hcl` with a local or `github.com` source so teammates get it via `tflint --init`.

5. **Wire into pre-commit** — a `.pre-commit-config.yaml` hook running `tflint` per directory, plus the CI job.

6. **Gating strategy** — DESTRUCTIVE-to-velocity warning: a false-positive custom rule set to `error` severity can BLOCK every deploy. Recommend rolling out new rules at `warning` first, promoting to `error` only after a clean baseline, and how to suppress with `tflint-ignore` comments.

Output: (a) the `.tflint.hcl`, (b) config-vs-code decision per rule, (c) the Go rule + `main.go`, (d) pre-commit + CI wiring, (e) the severity rollout plan.

Bias toward: built-in rules over custom code, warning-before-error rollout, and per-directory linting.

---
Input template:
```
Conventions to enforce:
Provider(s) + TFLint version:
Where linting runs today:
Gating strictness (advisory / blocking):
Distribution (local build / GitHub release):
```

Run this prompt with AI

Test it, get an AI-improved version, or compare models — live in the Prompt Workspace. No copy-paste.

Why this prompt works

Most teams reach for a custom Go plugin when a two-line config change on the bundled terraform ruleset would do the job. This prompt forces the decision explicitly — config rule vs. compiled plugin — before any Go gets written, which is where most TFLint effort is wasted. It also treats the real hazard honestly: TFLint can’t destroy infrastructure, but a badly scoped custom rule at error severity halts every pipeline, so the prompt bakes in a warning-first rollout.

How to use it

  1. List the conventions you actually want enforced and paste them into the template.
  2. Run the prompt; implement the config-only rules first and confirm they lint clean.
  3. Only build the Go plugin for checks no bundled rule covers.
  4. Add the pre-commit hook so violations are caught before push, and mirror it in CI.
  5. Ship new rules as warning, watch the baseline, then flip to error.

Useful commands

# Install plugins declared in .tflint.hcl
tflint --init

# Lint the current module; --recursive walks nested modules
tflint --recursive

# Machine-readable output for CI annotations
tflint -f json --recursive > tflint-report.json

# Build and install a custom ruleset plugin locally
go build -o ~/.tflint.d/plugins/tflint-ruleset-org .

# Run only your custom rule to iterate quickly
tflint --enable-rule=org_required_cost_center_tag --recursive

Patterns

.tflint.hcl enabling the bundled terraform preset, the pinned aws ruleset, and a config-driven naming-convention rule (no Go required):

plugin "terraform" {
  enabled = true
  preset  = "recommended"
}

plugin "aws" {
  enabled = true
  version = "0.32.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

plugin "org" {
  enabled = true
  version = "0.1.0"
  source  = "github.com/example-org/tflint-ruleset-org"
}

rule "terraform_naming_convention" {
  enabled = true
  format  = "snake_case"

  resource {
    format = "snake_case"
  }
}

A custom Go rule on the TFLint plugin SDK enforcing a required cost-center tag, with the main.go that serves the ruleset:

package main

import (
	hcl "github.com/hashicorp/hcl/v2"
	"github.com/terraform-linters/tflint-plugin-sdk/hclext"
	"github.com/terraform-linters/tflint-plugin-sdk/plugin"
	"github.com/terraform-linters/tflint-plugin-sdk/tflint"
)

type RequiredCostCenterTagRule struct {
	tflint.DefaultRule
}

func (r *RequiredCostCenterTagRule) Name() string     { return "org_required_cost_center_tag" }
func (r *RequiredCostCenterTagRule) Enabled() bool    { return true }
func (r *RequiredCostCenterTagRule) Severity() tflint.Severity { return tflint.WARNING }
func (r *RequiredCostCenterTagRule) Link() string     { return "" }

func (r *RequiredCostCenterTagRule) Check(runner tflint.Runner) error {
	// Inspect the `tags` attribute of every aws_instance in the module.
	resources, err := runner.GetResourceContent("aws_instance", &hclext.BodySchema{
		Attributes: []hclext.AttributeSchema{{Name: "tags"}},
	}, nil)
	if err != nil {
		return err
	}

	for _, resource := range resources.Blocks {
		attr, ok := resource.Body.Attributes["tags"]
		if !ok {
			runner.EmitIssue(r, "missing tags block; cost-center is required", resource.DefRange)
			continue
		}
		var tags map[string]string
		if err := runner.EvaluateExpr(attr.Expr, &tags, nil); err != nil {
			return err
		}
		if _, found := tags["cost-center"]; !found {
			runner.EmitIssue(r, "resource must set the cost-center tag", attr.Expr.Range())
		}
	}
	return nil
}

func main() {
	plugin.Serve(&plugin.ServeOpts{
		RuleSet: &tflint.BuiltinRuleSet{
			Name:    "org",
			Version: "0.1.0",
			Rules:   []tflint.Rule{&RequiredCostCenterTagRule{}},
		},
	})
}

var _ hcl.Diagnostics // keep hcl import explicit for range types

Pre-commit wiring so violations are caught before code ever reaches CI:

repos:
  - repo: https://github.com/terraform-linters/tflint
    rev: v0.53.0
    hooks:
      - id: tflint
        args: ["--recursive"]

Related prompts

More Terraform prompts & error guides

Browse every Terraform prompt and troubleshooting guide in one place.

Free download · 368-page PDF

Reading prompts? Get all 500 in one free PDF

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.