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.

# Generate a starter opsight.yaml (optional)
opsight init

# 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

# Lint sub-directories / modules too (tflint --recursive)
opsight scan ./infra --recursive

# 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, sarif, both
--no-cost false Skip the Infracost scanner
--no-security false Skip the Checkov scanner
--recursive (config) Run tflint over sub-directories/modules
--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

Generate a commented starter file with every default spelled out:

opsight init            # writes ./opsight.yaml (use --dir / --force as needed)

The file is validated against a schema on load (Pydantic): unknown keys (typos) and wrong types fail fast with a clear error rather than being silently ignored. Opsight reads an opsight.yaml file (override with --config). Defaults:

# Opsight default configuration
scanners:
  tflint:
    enabled: true
    recursive: false        # also lint sub-directories/modules (tflint --recursive)
    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.
  • SARIFopsight.sarif (--format sarif): SARIF 2.1.0 for GitHub code scanning. Findings land in the repo's Security tab and as PR annotations, with severity buckets driven by security-severity.

CI integration (GitHub Action & SARIF)

Use the bundled composite action to scan Terraform and upload findings to GitHub code scanning on every push/PR:

# .github/workflows/opsight.yml
name: Opsight
on: [push, pull_request]

permissions:
  contents: read
  security-events: write        # required to upload SARIF

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Trifel-guy/Opsight@v1
        with:
          path: ./infra          # Terraform directory (default ".")
          fail-under: 70          # optional: fail the job below this score

Prefer to wire it yourself? Run the CLI and upload the SARIF directly:

      - run: pip install "opsight[scanners]"
      - run: opsight scan ./infra --format sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: opsight.sarif

Note: uploading SARIF to code scanning is free on public repositories; on private repos it requires GitHub Advanced Security. The tflint / terraform binaries aren't installed by the action — add their setup steps first if you want those scanners too (checkov ships via opsight[scanners]).


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.8.1.tar.gz (48.5 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.8.1-py3-none-any.whl (59.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for opsight-0.8.1.tar.gz
Algorithm Hash digest
SHA256 745c2f3df00eafd7f04d02709272fba587132ffc9f48041d5813decf917af46f
MD5 e782047d78e24df7c3a1d29a51bf7e4f
BLAKE2b-256 b530f06678af792214f77b3bdfaed4a53d8ed84051d11f59313c55569c5949d7

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: opsight-0.8.1-py3-none-any.whl
  • Upload date:
  • Size: 59.5 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.8.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8289bc53cd80743a4a1815a6c33f73311af9eac70af909efefdae56c2aa5c986
MD5 4f499ea18ae0b5fd6b8022dfcc52fc6f
BLAKE2b-256 160c82524e1120f2ddf888bbd8f0c3308aefb4f5f0123e3132709d897c769480

See more details on using hashes here.

Provenance

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