Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ All notable changes to the Azure Pricing MCP Server will be documented in this f
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.4.0] - 2026-03-03

### Added

- **Azure Databricks DBU Pricing Tools** (contributed by PR #28)
- `databricks_dbu_pricing` - Search and list Azure Databricks DBU rates by workload type, tier, and region
- `databricks_cost_estimate` - Estimate monthly and annual Databricks costs based on DBU consumption
- `databricks_compare_workloads` - Compare DBU costs across workload types or regions
- Supports 14 workload types with fuzzy alias matching (e.g., 'etl' -> 'jobs', 'warehouse' -> 'serverless sql')
- Real-time pricing from Azure Retail Prices API — no authentication required
- Photon pricing comparison included automatically

### Changed

- **Orphaned Resource Detection** expanded from 5 to 11 resource types (contributed by [@iditbnaya](https://github.com/iditbnaya), PR #30)
- Removed NICs and NSGs (no cost impact — not billable resources)
- Added: SQL Elastic Pools, Application Gateways, NAT Gateways, Load Balancers, Private DNS Zones, Private Endpoints, Virtual Network Gateways, DDoS Protection Plans
- Fixed SQL Elastic Pools query to correctly filter for pools with no databases (leftanti join)
- Fixed Private Endpoints query to check both auto-approved and manual-approval connections
- Updated all documentation (FEATURES.md, ORPHANED_RESOURCES.md, TOOLS.md, USAGE_EXAMPLES.md)

### Documentation

- Added Databricks DBU pricing tools to TOOLS.md
- Updated orphaned resource documentation across all docs

## [3.3.0] - 2026-02-12

### Added
Expand Down Expand Up @@ -36,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Orphaned Resource Detection Tool** (contributed by [@iditbnaya](https://github.com/iditbnaya))
- `find_orphaned_resources` - Detect orphaned Azure resources and compute wasted costs
- Scans for unattached managed disks, orphaned NICs, public IPs, NSGs, and empty App Service Plans
- Initial release: scans for unattached managed disks, orphaned NICs, public IPs, NSGs, and empty App Service Plans
- Integrates with Azure Cost Management API for historical cost lookup
- Groups results by resource type with per-type summary tables
- Configurable lookback period (default: 60 days)
Expand Down
12 changes: 9 additions & 3 deletions docs/USAGE_EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ Subscriptions scanned: 3
|---------------|-------|-----------|
| Unattached Disk | 2 | $85.00 |
| Orphaned Public IP | 2 | $42.50 |
| Orphaned NSG | 1 | $0.00 |
| Orphaned Load Balancer | 1 | $18.25 |
```

### Custom Lookback Period
Expand All @@ -395,10 +395,16 @@ The tool detects these orphaned resource types:
| Resource Type | Detection Criteria |
|---------------|--------------------|
| **Unattached Disk** | Managed disks with no `managedBy` reference |
| **Orphaned NIC** | Network interfaces not attached to a VM or private endpoint |
| **Orphaned Public IP** | Public IPs not associated with any resource |
| **Orphaned NSG** | Network security groups not attached to any NIC or subnet |
| **Empty App Service Plan** | App Service Plans with zero hosted apps |
| **Orphaned SQL Elastic Pool** | SQL Elastic Pools with no databases in the pool |
| **Orphaned Application Gateway** | Application gateways with no backend address pools or targets |
| **Orphaned NAT Gateway** | NAT gateways not associated with any subnet |
| **Orphaned Load Balancer** | Load balancers with no backend address pools |
| **Orphaned Private DNS Zone** | Private DNS zones with no virtual network links |
| **Orphaned Private Endpoint** | Private endpoints with no connections or unapproved connections |
| **Orphaned Virtual Network Gateway** | Virtual network gateways with no IP configurations |
| **Orphaned DDoS Protection Plan** | DDoS protection plans with no associated virtual networks |

### Cost Analysis

Expand Down
7 changes: 4 additions & 3 deletions src/azure_pricing_mcp/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,8 @@ def format_orphaned_resources_response(result: dict[str, Any]) -> str:
f"Scanned {len(subscriptions)} subscription(s) — "
"no orphaned disks, public IPs, App Service Plans, SQL Elastic Pools, "
"Application Gateways, NAT Gateways, Load Balancers, Private DNS Zones, "
"Private Endpoints, Virtual Network Gateways, or DDoS Protection Plans detected."
"Private Endpoints, Virtual Network Gateways, "
"or DDoS Protection Plans detected."
)

# Collect all orphaned resources across subscriptions
Expand Down Expand Up @@ -697,9 +698,9 @@ def format_orphaned_resources_response(result: dict[str, Any]) -> str:
return "\n".join(response_lines)


# ---------------------------------------------------------------------------
# =============================================================================
# PTU Sizing + Cost Planner
# ---------------------------------------------------------------------------
# =============================================================================


def format_ptu_sizing_response(result: dict[str, Any]) -> str:
Expand Down
20 changes: 17 additions & 3 deletions src/azure_pricing_mcp/services/orphaned_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@
"""

# Resource Graph query for orphaned SQL Elastic Pools (no databases)
# Uses leftanti join: keep only pools that have NO matching database.
ORPHANED_SQL_ELASTIC_POOLS_QUERY = """
Resources
| where type =~ 'microsoft.sql/servers/elasticpools'
| extend poolId = tolower(id)
| join kind=leftanti (
Resources
| where type =~ 'microsoft.sql/servers/databases'
| where isnotempty(properties.elasticPoolId)
| extend poolId = tolower(properties.elasticPoolId)
) on poolId
| project id, name, type, location, resourceGroup,
subscriptionId,
sku = tostring(sku.name),
Expand Down Expand Up @@ -123,12 +131,18 @@
"""

# Resource Graph query for orphaned Private Endpoints (no connections or not approved)
# Checks both auto-approved (privateLinkServiceConnections) and manual-approval
# (manualPrivateLinkServiceConnections) arrays to avoid false positives.
ORPHANED_PRIVATE_ENDPOINTS_QUERY = """
Resources
| where type =~ 'microsoft.network/privateendpoints'
| where isnull(properties.privateLinkServiceConnections)
or array_length(properties.privateLinkServiceConnections) == 0
or properties.privateLinkServiceConnections[0].properties.privateLinkServiceConnectionState.status != 'Approved'
| extend autoConns = array_length(properties.privateLinkServiceConnections)
| extend manualConns = array_length(properties.manualPrivateLinkServiceConnections)
| extend autoStatus = tostring(properties.privateLinkServiceConnections[0].properties.privateLinkServiceConnectionState.status)
| extend manualStatus = tostring(properties.manualPrivateLinkServiceConnections[0].properties.privateLinkServiceConnectionState.status)
| where (isnull(autoConns) or autoConns == 0) and (isnull(manualConns) or manualConns == 0)
or ((isnull(autoConns) or autoConns == 0 or autoStatus != 'Approved')
and (isnull(manualConns) or manualConns == 0 or manualStatus != 'Approved'))
| project id, name, type, location, resourceGroup,
subscriptionId
"""
Expand Down