Skip to main content

Infrastructure intelligence engine — Terraform audit CLI

Project description

⚡ Opsight

CI PyPI Release Python Type checked: mypy strict Lint: ruff License: MIT

Infrastructure Intelligence Engine — a Terraform audit & scaffolding CLI: aggregate security, cost and quality findings into a single graded health report, and scaffold modules and providers from best-practice templates.

Opsight runs a suite of best-in-class scanners against a Terraform directory, normalizes their heterogeneous output into a canonical model, computes a weighted A→F health score, and produces rich terminal, JSON and HTML reports. It also generates Terraform boilerplate — new modules and provider blocks with sane version constraints — straight from the command line.


Table of contents


Features

  • 🔒 Security auditing via Checkov
  • 🔧 Quality / linting via tflint
  • 💰 Cost estimation via Infracost (optional, requires an API key)
  • 📊 Weighted health score per category + a global grade (A → F)
  • 🎯 Prioritized findings — sorted by severity then category importance
  • 📄 Multi-format reports — terminal (Rich), JSON and a self-contained HTML page
  • 🧩 Pluggable scanners — add a new tool by implementing a single run() method
  • ⚙️ Fully configurable via opsight.yaml (enable/disable scanners, weights, penalties)
  • 🚦 CI-friendly exit codes — non-zero when the global score drops below 60
  • 🏗️ Scaffolding — generate modules (module new) and add providers (provider add) with pessimistic ~> constraints resolved from the Terraform Registry
  • 🪄 op alias — every command is also available under the shorter op

How it works

Terraform dir
     │
     ▼
┌─────────────┐   raw dicts   ┌───────────────┐   Finding[]   ┌────────────────┐
│  Scanners   │ ────────────▶ │ Normalization │ ────────────▶ │ Prioritization │
│ tflint      │               │   service     │               │    service     │
│ checkov     │               └───────────────┘               └────────┬───────┘
│ infracost   │                                                        │
└─────────────┘                                                        ▼
                                                              ┌─────────────────┐
                                                              │  Scoring service │
                                                              └────────┬────────┘
                                                                       ▼
                                                        ScanReport → JSON / HTML / terminal
  1. Scan — every enabled scanner runs against the target path and returns raw, tool-specific dicts.
  2. Normalize — raw results are coerced into a canonical Finding model with a unified Severity and Category.
  3. Prioritize — findings are sorted (security first, critical first).
  4. Score — per-category and weighted global scores are computed and clamped to [0, 100].
  5. Report — results are rendered to the terminal and written to JSON / HTML.

Installation

From PyPI:

pip install opsight

Or with pipx to get an isolated, always-available CLI:

pipx install opsight

This exposes both the opsight command and its shorter op alias.

To pull in the checkov scanner at the same time, install the extra:

pip install "opsight[scanners]"

The other scanners (tflint, terraform, infracost) are external binaries — see Installing the scanners.

From source (for development)
# From the package directory (contains pyproject.toml):
python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate

pip install -e .                  # runtime install
pip install -e ".[dev]"           # with dev tooling (pytest, ruff, mypy)

Prerequisites

Opsight orchestrates external CLI tools — install the ones you need and make sure they are on your PATH:

Tool Purpose Required?
terraform parse / init infra recommended
tflint quality / linting for the quality scanner
checkov security scanning for the security scanner
infracost cost estimation optional — needs INFRACOST_API_KEY env var

Installing the scanners

checkov is a Python tool, so it ships as an optional extra — install it together with Opsight:

pip install "opsight[scanners]"      # Opsight + checkov

tflint, terraform and infracost are standalone Go binaries (not on PyPI), so pip cannot install them. Use your platform's package manager and make sure they are on your PATH:

Tool macOS (brew) Windows (scoop / choco) Linux
tflint brew install tflint scoop install tflint install script
terraform brew install terraform choco install terraform HashiCorp repo
infracost brew install infracost choco install infracost install script

Opsight skips any scanner whose binary is missing (with a warning) and still runs with whatever is available.

Infracost API key

Opsight never reads the key itself — it just runs the infracost CLI as a subprocess, which inherits the environment. So you provide the key exactly the way infracost expects, in one of two ways:

Option 1 — environment variable (the subprocess inherits it):

export INFRACOST_API_KEY="ico-xxxxxxxxxxxx"      # bash / Git Bash
$env:INFRACOST_API_KEY = "ico-xxxxxxxxxxxx"      # PowerShell, current session
setx INFRACOST_API_KEY "ico-xxxxxxxxxxxx"        # PowerShell, persistent (reopen shell)

Option 2 — Infracost's own credential store (no env var needed afterwards):

infracost auth login                       # opens the browser, fetches a free key
# or set it directly:
infracost configure set api_key ico-xxxxxxxxxxxx

Get a free key with infracost auth login or from the Infracost dashboard (Org Settings → API keys).

Then enable the scanner (it is off by default) in opsight.yaml:

scanners:
  infracost:
    enabled: true

Finally, run a scan without --no-cost, with infracost on your PATH. A cost result is only emitted as a finding when a resource's monthly cost exceeds $10.


Usage

Every command is also available under the shorter op alias — e.g. op scan ./infra, op module new vpc, op provider add aws.

# Scan a Terraform directory (writes JSON + HTML by default)
opsight scan ./infra

# Verbose — show the full findings table in the terminal
opsight scan ./infra -v

# Pick output formats and directory
opsight scan ./infra --format json,html --output ./reports

# Skip specific scanners
opsight scan ./infra --no-cost --no-security

# Use a custom configuration file
opsight scan ./infra --config my-opsight.yaml

# Print the version
opsight version

scan options

Option Alias Default Description
PATH (required) Path to the Terraform directory
--config -c opsight.yaml Path to a configuration file
--output -o . Output directory for reports
--format -f json,html Report formats: json, html, both
--no-cost false Skip the Infracost scanner
--no-security false Skip the Checkov scanner
--verbose -v false Show detailed findings in the terminal
--fail-under 60 Exit non-zero if the global score is below this

Exit codes

Code Meaning
0 Global score ≥ threshold
1 Global score < threshold, or invalid path

The threshold defaults to 60 and can be set with --fail-under (CLI) or scoring.fail_under (config) — the flag wins. This makes Opsight easy to drop into a CI pipeline as a quality gate.


Scaffolding

Beyond auditing, Opsight scaffolds Terraform boilerplate from versioned templates. All writes are explicit and safe — existing files are never silently merged.

module new <name>

Scaffold a reusable module under <dir>/modules/<name>/:

opsight module new networking          # creates ./modules/networking/
opsight module new vpc --dir infra     # creates ./infra/modules/vpc/
opsight module new vpc --force         # overwrite an existing module

It generates main.tf, variables.tf, outputs.tf, versions.tf (with a terraform { required_version = … } block) and a README.md. The command aborts if the target already exists, unless --force is given. Names must be lowercase kebab- or snake_case — no path separators or .. traversal.

Option Alias Default Description
NAME (required) Module name (kebab- or snake_case)
--dir -d . Base directory that contains modules/
--force false Overwrite an existing module directory

provider add <name> [--version]

Declare a provider the right way — required_providers in versions.tf and the provider "<name>" {} block in providers.tf (each file created if missing):

opsight provider add scaleway                    # resolve latest -> ~> X.Y
opsight provider add scaleway --version "~> 2.5"  # pin a constraint yourself
opsight provider add hashicorp/aws               # explicit namespace/type
opsight provider add scaleway --dir infra        # target another directory

Version resolution. Without --version, Opsight queries the Terraform Registry for the latest stable release and builds a pessimistic constraint — 2.76.1 becomes ~> 2.76 (i.e. >= 2.76.0, < 3.0.0), allowing minor/patch upgrades while blocking breaking majors. A bare name is resolved by trying hashicorp/<name> then <name>/<name>; pass an explicit namespace/type to be precise. If the registry is unreachable, supply --version to work fully offline.

Idempotency. If the provider is already declared, Opsight warns and changes nothing (exit code 3) — never a duplicate entry. When versions.tf already has a required_providers block, the new entry is inserted safely; if no such block can be located, Opsight refuses rather than attempt a fragile HCL merge.

Option Alias Default Description
NAME (required) Provider name or namespace/type
--version (auto) Version constraint, e.g. ~> 2.5
--dir -d . Target Terraform directory

Exit codes

Code Meaning
0 Success
1 Error (invalid name, module exists, registry down)
3 Provider already declared — skipped, nothing changed

terraform fmt is run on the generated files when terraform is on your PATH; otherwise it is skipped silently.


Configuration

Opsight reads an opsight.yaml file (override with --config). Defaults:

# Opsight default configuration
scanners:
  tflint:
    enabled: true
    extra_args: []
  checkov:
    enabled: true
    extra_args: ["--compact", "--quiet"]
  infracost:
    enabled: false          # requires INFRACOST_API_KEY env var
    extra_args: []
    cost_threshold: 10.0    # flag resources costing at least this much (USD/month)

scoring:
  base: 100
  weights:
    security: 1.5
    cost: 1.2
    quality: 1.0
    maintainability: 1.0
  penalties:
    low: 1
    medium: 3
    high: 7
    critical: 15
  fail_under: 60            # exit non-zero below this global score

output:
  formats: ["json", "html"]
  directory: "."
  json_filename: "opsight-report.json"
  html_filename: "opsight-report.html"

codegen:
  terraform_required_version: ">= 1.5.0"   # injected into generated versions.tf
  registry:
    base_url: "https://registry.terraform.io"
    cache_ttl: 86400        # cache resolved provider versions (seconds; 0 disables)

logging:
  level: "INFO"

Scoring model

Each category starts at base (100) and loses points for every finding:

penalty(finding) = severity_penalty × category_weight
category_score    = max(0, base − Σ penalties in that category)
global_score      = round( Σ(category_score × weight) / Σ weights ), clamped to [0, 100]

Severity penalties (default): low = 1, medium = 3, high = 7, critical = 15.

Category weights (default): security = 1.5, cost = 1.2, quality = 1.0, maintainability = 1.0.

Letter grades (from the global score):

Grade Score range
A 90 – 100
B 75 – 89
C 60 – 74
D 40 – 59
F 0 – 39

Output reports

  • Terminal — a Rich summary table (per-category scores, grade, finding counts) plus an optional detailed findings table with -v.
  • JSONopsight-report.json: machine-readable, full finding detail (id, title, category, severity, source, resource, file, line, description, impact, recommendation).
  • HTMLopsight-report.html: a self-contained page rendered from a Jinja2 template, suitable for sharing or archiving as a CI artifact.

Architecture

Opsight follows a hexagonal (ports & adapters) layout:

opsight/
├── domain/                     # Pure business core (no I/O)
│   ├── models/                 # Finding, Score, ScanResult
│   ├── value_objects/          # Severity, Category
│   ├── services/               # normalization, prioritization, scoring
│   └── codegen/                # ABC ports, models, services, value objects
├── application/                # Orchestration
│   ├── use_cases/              # RunScanUseCase
│   ├── dto/                    # ScanReportDTO
│   └── codegen/                # NewModuleUseCase, AddProviderUseCase
├── infrastructure/             # Adapters to the outside world
│   ├── scanners/               # tflint, checkov, infracost (+ base, factory)
│   ├── terraform/              # executor (init/plan/fmt), parser (python-hcl2)
│   └── codegen/                # jinja renderer, local FS, HTTP registry, tf runner
├── interfaces/
│   └── cli/                    # Typer entry point (main.py) + codegen sub-apps
├── shared/                     # Cross-cutting concerns
│   ├── config/                 # YAML loading
│   ├── logging/                # logger setup
│   └── reporting/              # JSON + HTML reporters
└── templates/                  # report.html.j2 + terraform/ HCL templates

Adding a new scanner: subclass BaseScanner, implement run(path) -> list[dict], set name, and register it in ScannerFactory. The domain layer needs no changes — normalization maps the raw output onto the canonical Finding model.

Codegen keeps the same discipline: business logic (templating decisions, version resolution, idempotency) lives in domain/codegen, while all I/O — filesystem, HTTP registry, the terraform process — sits behind ABC ports (TemplateRenderer, FileSystemPort, RegistryClient, TerraformRunner) with concrete adapters in infrastructure/codegen. HCL templates are versioned under templates/terraform/.


Development

pip install -e ".[dev]"

pytest                 # run the test suite (unit + integration)
pytest --cov=opsight   # with coverage
ruff check .           # lint
mypy opsight           # type-check (strict mode)

Tooling configuration lives in pyproject.toml:

  • ruff — line length 100, target py311
  • mypystrict = true
  • pytesttestpaths = ["tests"], verbose with short tracebacks

Roadmap

  • Scaffolding (Phase A)module new, provider add
  • Additional scanners (e.g. Trivy, OPA/Conftest)
  • SARIF output for native GitHub code-scanning integration
  • Baseline / diff mode (compare against a previous report)
  • Per-finding severity overrides and suppression rules

License

MIT — see pyproject.toml.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opsight-0.4.0.tar.gz (43.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opsight-0.4.0-py3-none-any.whl (54.3 kB view details)

Uploaded Python 3

File details

Details for the file opsight-0.4.0.tar.gz.

File metadata

  • Download URL: opsight-0.4.0.tar.gz
  • Upload date:
  • Size: 43.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for opsight-0.4.0.tar.gz
Algorithm Hash digest
SHA256 ad4564c6c0edce2739f9b0aa35ab50a91fb6f3ec10befd50c3527cf4e448e7e0
MD5 49fbd37162f06ce2d2d846d2eae10188
BLAKE2b-256 cd055d429db804e6d218fe07be8c614a67da9bd1b061be2994a01619387047c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsight-0.4.0.tar.gz:

Publisher: release.yml on Trifel-guy/Opsight

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opsight-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: opsight-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 54.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for opsight-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 623a310241aeb447ce8fe965442ba52d500f3a2586859fcc096c5b9bbe51e802
MD5 5c300cf7cae65897d9f8e526d93af75d
BLAKE2b-256 7aeec991fbd8960d7b58593f734b74d70466998e131926476e02536fe5b4a5f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsight-0.4.0-py3-none-any.whl:

Publisher: release.yml on Trifel-guy/Opsight

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page