Discovery-based scanning is an intelligent approach where the system first discovers what resources exist, then only performs checks on those resources. This eliminates wasted effort checking for resources that don't exist.
The main resource (e.g., Azure Subscription) scans the environment to discover what sub-resources are present:
🔍 Discovering Azure Resources...
✓ Found 5 Storage Accounts
✓ Found 2 SQL Servers
✓ Found 0 MySQL Servers
✓ Found 1 Key Vault
✓ Found 3 Network Security Groups
✓ Found 0 Virtual Machines
✓ Found 2 App Services
Only resources that were discovered are scanned:
📊 Running Checks...
[Azure Storage] Checking 4 policies on 5 accounts...
[Azure SQL] Checking 6 policies on 2 servers...
[Azure Key Vault] Checking 4 policies on 1 vault...
[Azure NSG] Checking 6 policies on 3 NSGs...
[Azure App Service] Checking 2 policies on 2 apps...
❌ Skipping MySQL checks - no resources found
❌ Skipping VM checks - no resources found
Resources implement the DiscoveryResource interface:
type DiscoveryResource interface {
ResourceSpec
Discover(ctx context.Context, asset Asset) (map[string]int, error)
}func (r *SubscriptionResource) Discover(ctx context.Context, asset core.Asset) (map[string]int, error) {
discovered := make(map[string]int)
// Quickly count each resource type
storageClient := armstorage.NewAccountsClient(subscriptionID, cred, nil)
count := 0
pager := storageClient.NewListPager(nil)
for pager.More() {
page, _ := pager.NextPage(ctx)
count += len(page.Value)
}
if count > 0 {
discovered["azure_storage_account"] = count
}
// Repeat for each resource type...
return discovered, nil
}The scanner uses discovery results to filter checks:
// Run discovery if supported
if discoverer, ok := resource.(core.DiscoveryResource); ok {
discovered, err := discoverer.Discover(ctx, asset)
if err == nil {
// Filter checks based on discovered resources
for checkID, check := range checks {
if discovered[check.Resource] == 0 {
skip(checkID) // Resource type not found
}
}
}
}- No time wasted on non-existent resources
- Discovery is lightweight (just counts, no deep inspection)
- Parallel discovery for multiple resource types
- Clear visibility into what exists
- No confusing "No resources found" errors
- Accurate progress reporting
- Fewer API calls to Azure
- Reduced scanning time = lower compute costs
- Early exit for empty subscriptions
Scan Summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Discovered: 13 resources across 5 types
Checked: 28 policies on 13 resources
Passed: 21 checks
Failed: 7 checks
Skipped: 14 checks (no resources)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Azure Subscription discovers all resource types
discovered, _ := subscription.Discover(ctx, asset)
// Returns:
// {
// "azure_storage_account": 5,
// "azure_sql_server": 2,
// "azure_keyvault_vault": 1,
// "azure_network_security_group": 3,
// "azure_app_service": 2
// }Just count resources without fetching details:
- Speed: Very fast
- Accuracy: 100%
- Cost: Minimal API calls
Fetch minimal metadata during discovery:
- Speed: Moderate
- Accuracy: Can pre-filter based on properties
- Cost: Low API calls
Fetch all resource data during discovery:
- Speed: Slower
- Accuracy: Can skip checks based on actual data
- Cost: Same as full scan
Perfect for cloud providers where resource existence is uncertain:
- Azure Subscriptions
- AWS Accounts
- GCP Projects
Discover what files/features exist:
- GitHub repos with/without Actions
- Repos with/without Dependabot
- Repos with specific file types
Discover installed software/features:
- Installed packages
- Running services
- Available hardware
Without Discovery:
⏱️ Scan Time: 45s
📞 API Calls: 150
✓ Passed: 12
✗ Failed: 8
⊘ Skipped: 0
❌ Errors: 20 (no resources)
With Discovery:
⏱️ Discovery: 5s
⏱️ Scan Time: 18s
📞 API Calls: 65
✓ Passed: 12
✗ Failed: 8
⊘ Skipped: 20 (smart)
❌ Errors: 0
Total savings: 51% faster, 57% fewer API calls, 0 errors
Cache discovery results for subsequent scans:
// First scan: discovery + scan
// Second scan: use cached discovery (if recent)Discover resources as needed:
// Only discover resource types that have checks in the policyPolicy hints for optimization:
queries:
- uid: check-storage
resource: azure_storage_account
discover_on: azure_subscription # Hint for discovery parentDiscover multiple resource types in parallel:
go func() { discovered["sql"] = discoverSQL() }()
go func() { discovered["storage"] = discoverStorage() }()Existing resources continue to work without discovery:
// Without discovery: works as before
resource.Fetch(ctx, asset) // May return empty list
// With discovery: smart scanning
if discoverer, ok := resource.(DiscoveryResource); ok {
counts, _ := discoverer.Discover(ctx, asset)
if counts[resource.Name()] == 0 {
skip() // Don't even try to fetch
}
}No breaking changes - discovery is purely additive!