Metadata-Version: 2.4
Name: az-costguard
Version: 0.1.0
Summary: Azure cost optimization scanner — 204 rules across 12 domains
Author-email: Ragnild <hello@ragnild.com>
License: MIT
Project-URL: Homepage, https://github.com/ragnild/az-costguard
Project-URL: Source, https://github.com/ragnild/az-costguard
Project-URL: Issues, https://github.com/ragnild/az-costguard/issues
Keywords: azure,finops,cost-optimization,cloud,scanner,cost-management,devops,infrastructure
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer[all]>=0.12
Requires-Dist: rich>=13
Requires-Dist: azure-identity>=1.17
Requires-Dist: azure-mgmt-resourcegraph>=8.0
Requires-Dist: azure-mgmt-advisor>=9.0
Requires-Dist: azure-mgmt-subscription>=3.1
Requires-Dist: azure-monitor-query>=1.3
Requires-Dist: azure-mgmt-costmanagement>=4.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: setuptools>=70; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# az-costguard

Self-hosted Azure cost optimization scanner. Runs 204 rules across 12 FinOps domains — waste elimination, rightsizing, networking, storage, monitoring, and more — and reports findings with estimated monthly savings.

---

## Prerequisites

| Requirement | Version |
|---|---|
| Python | 3.11+ |
| Azure CLI | Any (for `az login`) |
| Azure RBAC | `Reader` on target subscriptions |

## Required Azure Permissions

The minimum IAM role is **Reader** assigned at the subscription scope (or management group for multi-subscription scans). No write permissions are required — az-costguard is read-only.

The following Azure APIs are called during a scan:

| API | Used for |
|-----|----------|
| Azure Resource Graph | Resource inventory queries (KQL) |
| Azure Monitor Metrics | CPU, memory, connection utilisation |
| Azure Advisor | Recommendation signals |
| Azure Cost Management | Commitment coverage and spend data |
| Azure Subscription API | Subscription name resolution |

For CI/CD or automated scans, create a Service Principal with the `Reader` role:

```bash
az ad sp create-for-rbac \
  --name az-costguard \
  --role Reader \
  --scopes /subscriptions/<subscription-id>
```

Set the output values as environment variables `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`.

---

## Installation

### From PyPI (recommended)

```bash
pip install az-costguard
```

### From source (development)

```bash
git clone https://github.com/ragnild/az-costguard.git
cd az-costguard

python -m venv .venv

# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -e ".[dev]"
```

### Docker

```bash
git clone https://github.com/ragnild/az-costguard.git
cd az-costguard
docker build -t az-costguard:latest .
```

Run a scan using a service principal:

```bash
docker run --rm \
  -e AZURE_TENANT_ID="<tenant-id>" \
  -e AZURE_CLIENT_ID="<client-id>" \
  -e AZURE_CLIENT_SECRET="<client-secret>" \
  az-costguard:latest scan -s <subscription-id>
```

Save findings to a file on your host:

```bash
docker run --rm \
  -e AZURE_TENANT_ID="<tenant-id>" \
  -e AZURE_CLIENT_ID="<client-id>" \
  -e AZURE_CLIENT_SECRET="<client-secret>" \
  -v "$PWD/output:/output" \
  az-costguard:latest scan -s <subscription-id> --format csv --output /output/findings.csv
```

> **Windows (PowerShell):** replace `\` line continuation with a backtick `` ` `` and `$PWD` with `${PWD}`.

---

## Authentication

### Option 1 — Azure CLI (recommended for local use)

```bash
az login
az account set --subscription <subscription-id>
```

### Option 2 — Service Principal

Set these environment variables before running:

```bash
AZURE_TENANT_ID=<tenant-id>
AZURE_CLIENT_ID=<client-id>
AZURE_CLIENT_SECRET=<client-secret>
```

az-costguard automatically picks up the service principal when all three variables are present.

---

## Running a Scan

### Scan all accessible subscriptions

```bash
az-costguard scan
```

### Scan a specific subscription

```bash
az-costguard scan -s <subscription-id>
```

### Scan multiple subscriptions

```bash
az-costguard scan -s <sub-id-1> -s <sub-id-2>
```

### Verbose mode — see every rule result

```bash
az-costguard scan -s <subscription-id> --verbose
```

Shows a **Rule Execution Detail** table with the status of every evaluated rule (PASS / FOUND / ERROR / SKIPPED), resource names, locations, and estimated savings.

---

## Output Formats

### Table (default)

Prints a **Category Summary** and **Overall Summary** to the terminal.

```bash
az-costguard scan -s <subscription-id>
```

### Table + Rule Execution Detail

```bash
az-costguard scan -s <subscription-id> --verbose
```

### JSON

Outputs the findings array as JSON to stdout. Pipe or redirect as needed.

```bash
az-costguard scan -s <subscription-id> --format json

# Save to file
az-costguard scan -s <subscription-id> --format json > findings.json
```

### CSV

Outputs findings as a CSV file with a header row.

```bash
az-costguard scan -s <subscription-id> --format csv

# Save to file
az-costguard scan -s <subscription-id> --format csv > findings.csv
```

CSV columns: `rule_id`, `rule_name`, `domain`, `severity`, `resource_name`, `resource_id`, `resource_group`, `subscription_id`, `location`, `monthly_savings`, `annual_savings`, `confidence`, `risk`, `evidence`, `description`, `recommendation`, `suppressed`

### HTML Executive Report

Generate a self-contained interactive HTML report from a CSV export. Opens in any browser — no server required.

```bash
# Step 1 — export findings to CSV
az-costguard scan -s <subscription-id> --format csv > findings.csv

# Step 2 — generate HTML report
az-costguard report --csv findings.csv --output report.html
```

For multiple subscriptions, pass `--csv` once per file:

```bash
az-costguard report --csv sub1.csv --csv sub2.csv --output report.html
```

---

## Filtering

### By domain

```bash
az-costguard scan -s <subscription-id> --domain "Waste Elimination"
```

Available domains:

- `Waste Elimination`
- `VM Rightsizing`
- `Disk Optimization`
- `Azure SQL`
- `App Services & Functions`
- `AKS`
- `Networking`
- `Storage Accounts`
- `Data Services`
- `Monitoring & Backup`
- `Commitments`
- `Governance & FinOps Intelligence`

### By minimum severity

```bash
az-costguard scan -s <subscription-id> --severity HIGH
az-costguard scan -s <subscription-id> --severity MEDIUM
az-costguard scan -s <subscription-id> --severity LOW
```

### Combining filters

```bash
az-costguard scan -s <subscription-id> --domain "Networking" --severity MEDIUM --format csv > network-findings.csv
```

---

## Command Reference

```
Usage: az-costguard [OPTIONS] COMMAND [ARGS]...

Options:
  -V, --version   Show version and exit.
  --help          Show this message and exit.

Commands:
  scan    Scan Azure subscriptions for cost optimization findings.
  report  Generate an interactive HTML executive report from CSV exports.
```

### scan

```
Usage: az-costguard scan [OPTIONS]

Options:
  -s, --subscription TEXT   Subscription ID to scan. Repeatable.
                            Defaults to all accessible subscriptions.
  -d, --domain TEXT         Limit to a single domain.
  --severity TEXT           Minimum severity: HIGH, MEDIUM, or LOW.
  -f, --format TEXT         Output format: table, json, or csv.  [default: table]
  -c, --config TEXT         Path to az-costguard.yaml config file.
  -v, --verbose             Show rule execution detail for every evaluated rule.
  --help                    Show this message and exit.
```

### report

```
Usage: az-costguard report [OPTIONS]

  Generate an interactive HTML executive report from one or more scan CSV files.

Options:
  --csv     PATH   CSV file produced by 'scan --format csv'. Repeatable.  [required]
  --output  PATH   Output HTML file path.  [default: az_costguard_report.html]
  --help           Show this message and exit.
```

---

## Configuration File

Create `az-costguard.yaml` to set defaults or suppress known findings:

```yaml
min_monthly_savings: 10.0   # Only report findings with >= $10/month savings

exceptions:
  - rule_id: azure.publicip.unused
    resource_id: /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/publicIPAddresses/<name>
```

Pass the config file with:

```bash
az-costguard scan -s <subscription-id> --config az-costguard.yaml
```

---

## Example Output

### Default mode

```
                      CATEGORY SUMMARY
 Category                    Evaluated  Triggered  Findings  Est. $/mo
 ──────────────────────────────────────────────────────────────────────
 Monitoring & Backup               3          3         4         —
 Networking                       11          2         8        $8
 Waste Elimination                12          2         2         —
 ──────────────────────────────────────────────────────────────────────
 Total                            26          7        14        $8

                      OVERALL SUMMARY
 ┌──────────────────────────────┬────────────────────────────────┐
 │ Subscriptions scanned        │ 1                              │
 │ Resources in scope           │ 4,823                          │
 │ Rules evaluated              │ 35                             │
 │ Total findings               │ 13                             │
 │ Estimated monthly savings    │ $7.30                          │
 ├──────────────────────────────┼────────────────────────────────┤
 │ Severity breakdown           │ HIGH: 0  MEDIUM: 13  LOW: 0    │
 ├──────────────────────────────┼────────────────────────────────┤
 │ Scan duration                │ 18.3s                          │
 └──────────────────────────────┴────────────────────────────────┘
```

### Verbose mode — Rule Execution Detail (excerpt)

```
 Rule ID                           Status   Found  Risk    Est.$/mo  Resource(s)          Location    Reason
 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
 azure.vm.stopped_not_deallocated   PASS         0  —           —     —                    —           —
 azure.vm.orphaned.nic              FOUND        6  MEDIUM      —     nic-001, nic-002 +4   uaenorth    Orphaned NIC not attached to any VM
 azure.publicip.unused              FOUND        2  MEDIUM     $8     pip-001, pip-002      uaenorth    PIP has no IP configuration. SKU: Standard
 azure.bastion.idle                 FOUND        1  MEDIUM      —     bastion-dev           uaenorth    Verify session activity
 azure.acr.unused                   SKIPPED      —  —           —     —                    —           No executor yet

 Rules evaluated  : 29
 Passed           : 24
 Triggered        :  5
 Errors           :  0
 Skipped          :  6

 Estimated savings from triggered rules : $7.30/month
```

---

## Domains & Rule Count

| Domain | Rules |
|---|---|
| Waste Elimination | 35 |
| VM Rightsizing | 27 |
| Disk Optimization | 14 |
| Azure SQL | 14 |
| App Services & Functions | 11 |
| AKS | 24 |
| Networking | 9 |
| Storage Accounts | 15 |
| Data Services | 12 |
| Monitoring & Backup | 11 |
| Commitments | 12 |
| Governance & FinOps Intelligence | 20 |
| **Total** | **204** |

---

## Version

```bash
az-costguard --version
```
