rossedman/tfmetrics

Terraform metrics
from static parsing.

Scan every Terraform repo in your GitHub org and emit one consistent set of metrics: inventory, version drift, module adoption and hygiene. No init, no plan, no state.

$ brew install rossedman/tap/tfmetrics
~/infra
$ tfmetrics scan ./infra
scanned acme/infra: 5 modules -> facts/acme__infra.json
$ tfmetrics explain facts/ | grep -A2 hygiene
hygiene score 65.3 / 100
Remote module calls pinned 5/6 weight 30
Variables and outputs described 4/8 weight 15
$ tfmetrics aggregate facts/ --offline --sink openmetrics
aggregated 1 repos (1 with Terraform) into 36 series
$
  • 17gauges in the metrics catalog
  • 6sinks, from Prometheus to Parquet
  • 0.5 sto scan 200 repos on 4 cores
  • 0terraform runs, state reads or cloud APIs

The problem

Terraform sprawl.
Nobody has counted it.

Which version of the EKS module are three hundred repos on? Which roots have no required_version? Answering means running terraform init everywhere or grepping and hoping. tfmetrics parses the HCL on disk and counts, the same way for every repo.

  • Static: no init, plan, state or cloud credentials
  • One facts file per repo, cached by commit SHA
  • A broken file is a diagnostic, never a crash
acme/infra65.3 / 100 @ 1c35bc0af618

Hygiene score

5 modules (2 root, 3 child) · 8 module calls · 1 parse error
github.com/acme/tf-iam is called with no reflive/prod · remote module call, pinned: none
Remote module calls pinned 5/6 weight 30 Variables and outputs described 4/8 weight 15 Providers with a version constraint 2/5 weight 15 Roots with required_version 1/2 weight 15 Variables with a type 3/4 weight 15 Modules that parse cleanly 4/5 weight 10

Core ideas

Three ideas.
Everything else follows.

tfmetrics sorts directories into roots and children, records what it finds as plain JSON, and scores the result against checks you can reweight.

Roots and children

Every directory with Terraform in it

A root has a backend, a cloud block or a configured provider. Everything else is a child module.

live/prod      root   s3
live/staging   root   cloud
modules/vpc    child  -

Facts, not scans

The contract between scan and aggregate

Each repo becomes one JSON file. Re-aggregate it with new weights or labels without cloning again.

"address": "github.com/acme/tf-iam",
"source_kind": "git",
"version": "",
"pinned": "none"

Hygiene score

0 to 100, from six weighted checks

Pinned modules weigh most. Weights are yours to change, and a check with nothing to check passes.

pinned                30 x 5/6
described             15 x 4/8
provider_constraints  15 x 2/5
required_version      15 x 1/2
typed_vars            15 x 3/4
parse_clean           10 x 4/5
                      => 65.3

Why tfmetrics

Built for fleets.
Small enough to trust.

Whole orgs, filtered

Per-org name, topic and custom-property filters with excludes, or --local-dir for clones you already have.

Rescans only what changed

Facts are cached by commit SHA. A repo whose HEAD matches is not even cloned.

Static by design

Never runs terraform, never reads state, never evaluates an expression. It needs a GitHub token and nothing else.

Drift you can alert on

tf_module_versions_behind counts the newer major.minor release lines a pinned module has missed.

Six sinks

JSON, OpenMetrics file or Pushgateway, OTLP over gRPC, CSV, Parquet and a Markdown report for PRs.

Parse errors are data

A file that will not parse becomes a diagnostic and a tf_parse_errors count. The scan goes on.

Quick start

From install to metrics
in four commands.

You need Go and a directory with Terraform in it. No token, no cloud account.

Full getting-started guide
  1. Install

    $ brew install rossedman/tap/tfmetrics
  2. Scan a repo

    Writes one facts file.

    $ tfmetrics scan ./infra
  3. Read the report

    Inventory, score, pinning and diagnostics.

    $ tfmetrics explain facts/
  4. Write metrics

    Drop --offline to add version drift.

    $ tfmetrics aggregate facts/ --offline --sink openmetrics

How it works

Five stages.
One file in the middle.

The tfmetrics pipeline: discover, classify, parse and extract write one facts JSON file per repo; aggregate computes 17 gauges and a hygiene score and writes them to json, openmetrics, otlp, parquet, csv and markdown sinks.
Tap to open the full-size diagram

Count your Terraform.

Free and open source under the Apache 2.0 license.