This guide explains how to use the CloudZero Azure Insights integration tool with the v2.0 CLI.
- Overview
- CLI Quick Reference
- Commands
- Use Cases
- Understanding the Output
- Best Practices
- Troubleshooting
This tool bridges the gap between Azure Advisor and CloudZero by:
- Fetching cost optimization recommendations from Azure Advisor
- Checking for duplicates in CloudZero
- Creating CloudZero insights for new recommendations (1:1 mapping)
Key features:
- ✅ Modern CLI: Intuitive subcommand structure with excellent help text
- ✅ 1:1 mapping: One CloudZero insight per Azure recommendation
- ✅ Duplicate prevention: Automatically skips existing recommendations
- ✅ Enhanced metadata: Uses Azure categories, effort levels, and detailed descriptions
- ✅ Dry-run mode: Preview changes before applying
- ✅ Colored output: Clear visual feedback
- ✅ Progress tracking: Progress bars for long-running operations
# Using Makefile (Docker)
make validate # Check credentials
make dry-run # Preview sync
make run-sync # Full sync
make run-export # Export to CSV
make run-list LIMIT=20 # List 20 insights
make run-delete # Delete insights (interactive)
# Using CLI directly (local Python)
python -m app.cli validate # Check credentials
python -m app.cli sync --dry-run # Preview sync
python -m app.cli sync # Full sync
python -m app.cli export csv # Export to CSV
python -m app.cli list --limit 20 # List 20 insights
python -m app.cli delete # Delete insights
python -m app.cli <command> --help # Command-specific helpCheck that Azure and CloudZero credentials are configured correctly.
# Validate all credentials
make validate
# Using Docker run directly
docker run --rm --env-file .env cloudzero-azure-insights python -m app.cli validate# Validate all credentials
python -m app.cli validate
# Validate Azure only
python -m app.cli validate --azure
# Validate CloudZero only
python -m app.cli validate --cloudzeroValidating Azure credentials...
✓ Azure: Valid (3 subscription(s) found)
Validating CloudZero credentials...
✓ CloudZero: Valid
✓ All credentials validated successfully
When to use:
- Before first run
- After updating credentials
- Troubleshooting connectivity issues
- CI/CD pipeline validation
Upload Azure Advisor recommendations to CloudZero as insights.
Always run dry-run first to preview what will be uploaded:
# Using Makefile
make dry-run
# Using CLI directly
python -m app.cli sync --dry-runOutput:
🔍 DRY RUN MODE
Previewing what would be uploaded (no actual changes)
Initializing Azure and CloudZero clients...
Fetching Azure subscriptions...
✓ Found 3 subscription(s)
Fetching Azure Advisor recommendations...
✓ Retrieved 156 recommendation(s)
Checking for existing insights in CloudZero...
✓ 42 new recommendation(s) to sync
Syncing recommendations [####################################] 42/42
✓ [DRY RUN] Would create 42 insight(s)
After verifying dry-run output:
# Using Makefile
make run-sync
# Using CLI directly
python -m app.cli syncExpected behavior:
- Fetches all subscriptions from Azure
- Fetches Azure Advisor recommendations for each subscription
- Fetches existing CloudZero insights (can take 60-120 min for large deployments)
- Filters out duplicates by recommendation ID
- Uploads new recommendations as CloudZero insights with 1:1 mapping
- Shows progress bar during upload
Output:
Initializing Azure and CloudZero clients...
Fetching Azure subscriptions...
✓ Found 3 subscription(s)
Fetching Azure Advisor recommendations...
✓ Retrieved 156 recommendation(s)
Checking for existing insights in CloudZero...
✓ 42 new recommendation(s) to sync
⚠️ Using 1:1 mapping: One CloudZero insight per Azure recommendation
⚠️ This will create 42 insights (before duplicate filtering)
⚠️ Run with --dry-run first to preview changes
Syncing recommendations [####################################] 42/42
✓ Successfully created 42 insight(s)
What gets uploaded:
Each Azure recommendation becomes one CloudZero insight with:
- Title: Specific resource name and SKU change (e.g., "Right-size VM: vm-prod (D8 → B8)")
- Description: Detailed markdown with subscription, region, SKU, savings, utilization
- Cost Impact: Monthly savings amount
- Category: Mapped from Azure (cost, performance, etc.)
- Effort: Mapped from Azure impact (high, medium, low)
- Source: "Azure Advisor"
- Status: "new"
Export Azure Advisor recommendations to a local CSV file (without uploading to CloudZero).
# Export to ./output/azure_advisor_recommendations.csv
make run-export
# Export to custom directory (CLI direct only)
python -m app.cli export csv --output ./reports/# Export to default location (./output/)
python -m app.cli export csv
# Export to custom directory
python -m app.cli export csv --output ./reports/Output:
Initializing Azure client...
Fetching Azure subscriptions...
✓ Found 3 subscription(s)
Fetching Azure Advisor recommendations...
✓ Retrieved 156 recommendation(s)
Exporting to CSV in ./output/...
✓ Successfully exported to ./output/azure_advisor_recommendations.csv
CSV includes:
- Subscription ID
- Recommendation ID
- Resource name
- Current SKU
- Target SKU
- Monthly savings
- Annual savings
- Region
- Impact level
- All extended properties from Azure
Use cases:
- Auditing Azure recommendations
- Reporting to stakeholders
- Offline analysis
- Comparing recommendations over time
- Importing into spreadsheet tools
View existing CloudZero insights that were created from Azure Advisor with optional filters.
# List 10 insights (default)
python -m app.cli list
# List 20 insights
python -m app.cli list --limit 20
# List all insights (SLOW - can take hours!)
python -m app.cli list --limit 0All filters can be combined to narrow results:
# Filter by status
python -m app.cli list --status new
python -m app.cli list --status ignored
# Filter by Azure subscription
python -m app.cli list --subscription sub-abc-123
# Filter by cost impact
python -m app.cli list --min-cost 100 # >= $100
python -m app.cli list --max-cost 50 # <= $50
python -m app.cli list --min-cost 50 --max-cost 500 # Between $50-$500
# Filter by title
python -m app.cli list --title "Right-size" # Title contains "Right-size"
python -m app.cli list --title "Shutdown" # Title contains "Shutdown"
# Filter by timestamp (Unix timestamp)
python -m app.cli list --created-after 1705881600 # After Jan 22, 2024
python -m app.cli list --created-before 1708473600 # Before Feb 21, 2024
# Filter by Azure Advisor recommendation ID
python -m app.cli list --rec-id abc-123-def
# Combine multiple filters
python -m app.cli list --status new --min-cost 100 --limit 50
python -m app.cli list --subscription sub-123 --title "VM"| Filter | Type | Description |
|---|---|---|
--limit |
int | Number of insights to display (0=all) |
--subscription |
text | Azure subscription ID (substring match) |
--status |
choice | new, in_progress, completed, ignored |
--created-before |
int | Unix timestamp - created before this |
--created-after |
int | Unix timestamp - created after this |
--min-cost |
float | Minimum cost impact (inclusive) |
--max-cost |
float | Maximum cost impact (inclusive) |
--title |
text | Title substring (case-insensitive) |
--rec-id |
text | Azure Advisor recommendation ID |
--format |
choice | table (default) or json |
Output Example:
Fetching insights from CloudZero (limit=20)...
Filters applied: 1000 → 15 insights
✓ Displaying 15 of 15+ insights:
--------------------------------------------------------------------------------
1. Right-size VM: vm-firewall-prod (D8ls_v5 → B8als_v2)
Status: new | Cost Impact: $708 | Source: azure advisor
Created: 1771351712 | Updated: 1771351712
--------------------------------------------------------------------------------
2. Shutdown vm-test-dev
Status: new | Cost Impact: $1200 | Source: azure advisor
Created: 1771351713 | Updated: 1771351713
--------------------------------------------------------------------------------
...
Performance notes:
- Default limit (10): ~2-5 seconds
- Limit 100: ~10-30 seconds
- Limit 0 (all): 60-120 minutes for 10K-50K insights
- Filters are applied client-side after fetching
Delete Azure Advisor insights from CloudZero with optional filters.
# Step 1: Preview what will be deleted
python -m app.cli delete --dry-run
# Step 2: Review the output carefully
# Step 3: Actually delete (with confirmation)
python -m app.cli delete
# OR delete without confirmation
python -m app.cli delete --force# Delete ALL Azure Advisor insights (with confirmation)
python -m app.cli delete
# Preview deletion (no actual changes)
python -m app.cli delete --dry-run
# Delete without confirmation (dangerous!)
python -m app.cli delete --forceUse filters to delete specific subsets of insights:
# Delete by status
python -m app.cli delete --status ignored --dry-run
python -m app.cli delete --status completed --force
# Delete from specific Azure subscription
python -m app.cli delete --subscription sub-abc-123 --dry-run
# Delete low-value insights
python -m app.cli delete --max-cost 50 --dry-run
# Delete high-value insights (be careful!)
python -m app.cli delete --min-cost 1000 --dry-run
# Delete by title pattern
python -m app.cli delete --title "Shutdown" --dry-run
# Delete old insights (Unix timestamp)
python -m app.cli delete --created-before 1705881600 --dry-run
# Combine filters for precise targeting
python -m app.cli delete --status ignored --max-cost 100 --dry-runAll filters from list command work with delete:
| Filter | Type | Description |
|---|---|---|
--dry-run |
flag | Preview deletions without actually deleting |
--force |
flag | Skip confirmation prompt |
--subscription |
text | Azure subscription ID (substring match) |
--status |
choice | new, in_progress, completed, ignored |
--created-before |
int | Unix timestamp - delete insights created before this |
--created-after |
int | Unix timestamp - delete insights created after this |
--min-cost |
float | Minimum cost impact (inclusive) |
--max-cost |
float | Maximum cost impact (inclusive) |
--title |
text | Title substring (case-insensitive) |
--rec-id |
text | Azure Advisor recommendation ID |
Output Example (Dry-Run):
[DRY RUN] Previewing Azure Advisor insight deletions...
[DRY RUN] Would delete 15 Azure Advisor insights:
1. Shutdown vm-test-dev ($50)
2. Right-size VM: vm-old (D4 → B2) ($25)
3. Shutdown vm-temp ($30)
... and 12 more
Filters: status=ignored, max_cost<=50
[DRY RUN] No insights were actually deleted
Output Example (Actual Deletion):
Deleting Azure Advisor insights from CloudZero...
⚠️ Found 15 Azure Advisor insights to delete:
1. Shutdown vm-test-dev ($50)
2. Right-size VM: vm-old (D4 → B2) ($25)
... and 13 more
Filters: status=ignored, max_cost<=50
⚠️ WARNING: This will delete 15 Azure Advisor insights from CloudZero!
⚠️ Other CloudZero insights will NOT be affected.
Type 'DELETE' to confirm: DELETE
🗑️ Deleting 15 Azure Advisor insights...
✓ Deleted 15/15 Azure Advisor insights
When to use:
- Cleaning up test runs
- Removing old insights before v2.0 upgrade
- Resetting to fresh state
- Removing addressed/ignored insights
- Cleaning up low-value recommendations
Safety features:
- Dry-run mode - preview before deleting
- Interactive confirmation required (unless --force)
- Source locked - only touches "azure advisor" insights
- Filter preview - shows which filters are applied
- Paranoid double-checks before each deletion
- Reports success/failure counts
Schedule the tool to run daily and automatically sync new Azure recommendations.
# In cron or scheduled job
make validate && make dry-run && make run-syncRecommended:
- Run during off-hours (2-4 AM)
- First check credentials with
validate - Use
dry-runto preview (optional but recommended) - Then run
syncto upload new recommendations
See DEPLOYMENT.md for scheduling examples.
Export all recommendations to CSV for offline analysis:
make run-exportThen analyze ./output/azure_advisor_recommendations.csv in Excel or other tools.
If upgrading from v1.x with collapsed insights:
# 1. Export current state for backup
make run-list LIMIT=0 > insights_backup.txt
# 2. Delete old collapsed insights
make run-delete # Confirm deletion
# 3. Sync with new 1:1 mapping
make dry-run # Preview
make run-sync # UploadBefore deploying to production:
# Test Azure credentials
python -m app.cli validate --azure
# Test CloudZero credentials
python -m app.cli validate --cloudzero
# Test both
python -m app.cli validateBefore any sync:
# Always dry-run first
make dry-run
# Review output, then sync
make run-syncInitializing Azure and CloudZero clients... # Creating API clients
Fetching Azure subscriptions... # Listing accessible subscriptions
✓ Found 3 subscription(s) # Number of subscriptions found
Fetching Azure Advisor recommendations... # Fetching from Azure Advisor API
✓ Retrieved 156 recommendation(s) # Total recommendations across all subs
Checking for existing insights in CloudZero... # Fetching CloudZero insights
✓ 42 new recommendation(s) to sync # After filtering duplicates
Syncing recommendations [####] 42/42 # Progress bar
✓ Successfully created 42 insight(s) # Final count
| Message | Meaning |
|---|---|
✓ Found N subscription(s) |
Azure authentication succeeded |
✓ Retrieved N recommendation(s) |
Azure Advisor API call succeeded |
✓ N new recommendation(s) to sync |
After duplicate filtering |
ℹ All recommendations already exist |
Nothing new to sync |
⚠ N insight(s) failed |
Some uploads failed (check logs) |
❌ Error: CLOUDZERO_API_KEY required |
Missing environment variable |
The tool uses Python logging with these levels:
- INFO: Normal operations (subscription fetching, insight creation)
- WARNING: Non-critical issues (malformed recommendations, partial failures)
- ERROR: Failures (API errors, authentication issues)
- DEBUG: Detailed diagnostic info (not shown by default)
make validateEnsures credentials are working before attempting sync.
make dry-run # Preview
make run-sync # Then syncPrevents unexpected uploads.
First run can take 60-120 minutes for large deployments:
# In cron: Run daily at 3 AM
0 3 * * * cd /path/to/project && make run-sync# View logs for troubleshooting
docker logs <container-id>
# Or redirect to file
make run-sync > sync.log 2>&1# Quick check (fast)
make run-list LIMIT=10
# Full list (slow)
make run-list LIMIT=0# Regular exports for historical tracking
make run-export
mv output/azure_advisor_recommendations.csv backups/recommendations_$(date +%Y%m%d).csv# After testing
make run-deleteCause: Missing CloudZero API key
Solution:
# Check .env file exists
cat .env | grep CLOUDZERO_API_KEY
# Ensure Docker is loading .env
docker run --rm --env-file .env cloudzero-azure-insights env | grep CLOUDZEROCause: Azure credentials missing or invalid
Solution:
# Validate Azure setup
make validate
# Check environment variables
cat .env | grep AZURE
# Test Azure CLI
az account list # If using Azure CLICause: Service principal lacks Reader role
Solution:
- Ensure service principal has "Reader" role on target subscriptions
- Check Azure portal → Subscriptions → Access Control (IAM)
- See DEPLOYMENT.md for permission setup
Cause: Large number of existing CloudZero insights (10K-50K+)
Solution:
- This is normal for first run
- Schedule during off-hours
- Progress is saved incrementally
- Subsequent runs are faster (only new recommendations)
Cause: CloudZero API error
Solution:
# Check API status
curl -H "Authorization: $CLOUDZERO_API_KEY" https://api.cloudzero.com/v2/insights?limit=1
# Verify API key is valid
make validate
# Check logs for specific error
docker logs <container-id> | grep ERRORCause: Click not installed
Solution:
# Install dependencies
pip install -r requirements.txt
# Or install Click directly
pip install click==8.1.7Cause: Using v1.x flag-based CLI
Solution: Migrate to v2.0 subcommands:
# Old
python -m app.app --transmit --dry-run
# New
python -m app.cli sync --dry-runSee CLI Migration Guide in README.
# Show all commands
python -m app.cli --help
# Command-specific help
python -m app.cli sync --help
python -m app.cli export --help
python -m app.cli list --help
python -m app.cli delete --help
python -m app.cli validate --helpmake help- README - Overview and quick start
- DEPLOYMENT.md - Scheduling and deployment
- CLI Design - Technical design details
- GitHub Issues - Bug reports and feature requests
- support@cloudzero.com - CloudZero support team