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

Exit codes

Code Meaning
0 Global score ≥ 60 (grade C or better)
1 Global score < 60, or invalid path

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: []

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

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"

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.3.0.tar.gz (42.2 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.3.0-py3-none-any.whl (52.4 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for opsight-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5c85a63c48caf5eccf5fe059166b91ff72aaf837922ef2402586b154f2dab47c
MD5 c35278e9f33877d3baec9ec0c5188d72
BLAKE2b-256 c64bbcfdbbd45e1c0032e213a39ef100a5aeefd9f633299d0d3c9ff4059b64aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsight-0.3.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.3.0-py3-none-any.whl.

File metadata

  • Download URL: opsight-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 52.4 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7986e7cf862d2f1749fa2d31d423ef2a69bd09ab14115c10a4385507f81981e6
MD5 96fc525d80207d286b9e274d64ea0f15
BLAKE2b-256 7d801e9df054c93f70d760ea18e839c5b9a2912679d083023e39c593edc5a537

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsight-0.3.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