manifests/collectors/Hybrid/ArcSites.psd1

#
# Re-created for AB#6801. The prior manifest (converted from
# archived/Modules/Public/InventoryModules/Hybrid/ArcSites.ps1, AB#5660) declared three
# provider/type pairs that do not exist -- `microsoft.azurestackhci/sites`,
# `microsoft.edgeconfig/sites`, `microsoft.hybridcompute/sites` -- and was retired outright in
# AB#6842 rather than corrected, because at the time no verified replacement type was in hand.
#
# The audit's suggested correction, `Microsoft.Edge/sites`, is confirmed real:
# - listed in manifests/azure-provider-types.json (ARM's own provider/type catalog, 2026-07-31)
# - documented at https://learn.microsoft.com/azure/templates/microsoft.edge/2025-06-01/sites
# - it is Azure Arc site manager's site resource -- see
# https://learn.microsoft.com/azure/azure-arc/site-manager/overview
#
# It is NOT Resource-Graph-indexed, though: Resource Graph's supported-type reference
# (https://learn.microsoft.com/azure/governance/resource-graph/reference/supported-tables-resources)
# lists thirteen `microsoft.edge/*` types it carries and `sites` is not one of them. So this
# collector cannot read the `resources` table either -- it reads the per-subscription ARM REST
# list `Get-ScoutApiResources.ps1` now makes (`ArcSites` field), converted to resource rows by
# `ConvertTo-ScoutArcSiteResource.ps1`, the same non-RG-indexed shape AB#6769 established for
# Monitor/ResourceDiagnosticSettings.
#
# `Microsoft.Edge/sites` has no `tags` property in its schema (SiteProperties carries
# `displayName`, `description`, `siteAddress`, `labels` -- no `tags`), so this collector emits
# exactly one row per site rather than one row per tag.
#
@{
    ResourceTypes = @(
        'AZSC/ARMChild/ArcSites'
    )

    ResourceTypeMatching = 'Grouped'

    AdditionalFilter = $null

    FilterPreamble = ''

    RowLoopVariable = '1'

    Preamble = @'
$ResUCount = 1
            $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId }
            $data = $1.PROPERTIES
            # No `tags` property exists on this resource type (see the header note above); the
            # loop still needs a single-iteration source so exactly one row is emitted per site.
            $Tags = '0'
 
            $resourceGroupName = if ([string]::IsNullOrEmpty($1.RESOURCEGROUP)) { 'N/A (subscription-scoped)' } else { [string]$1.RESOURCEGROUP }
 
            $address = $data.siteAddress
            $addressLine = if ($null -eq $address) {
                'N/A'
            }
            else {
                $Parts = @(
                    $address.streetAddress1, $address.streetAddress2, $address.city,
                    $address.stateOrProvince, $address.postalCode
                ) | Where-Object { -not [string]::IsNullOrEmpty($_) }
                if ($Parts.Count -gt 0) { $Parts -join ', ' } else { 'N/A' }
            }
            $country = if ($null -ne $address -and -not [string]::IsNullOrEmpty($address.country)) { $address.country } else { 'N/A' }
 
            $labelCount = if ($data.labels) { @($data.labels.psobject.properties).Count } else { 0 }
'@


    AdditionalRowLoops = @()

    TagLoop = @{
        Variable = 'Tag'
        Source = '$Tags'
        Preamble = ''
    }

    Fields = @(
        @{
            Name = 'ID'
            Expression = '$1.id'
        }
        @{
            Name = 'Subscription'
            Expression = '(Get-AZSCSafeProperty -InputObject $sub1 -Path ''name'')'
        }
        @{
            Name = 'Resource Group'
            Expression = '$resourceGroupName'
        }
        @{
            Name = 'Name'
            Expression = '$1.NAME'
        }
        @{
            Name = 'Display Name'
            Expression = 'if ($data.displayName) { $data.displayName } else { ''N/A'' }'
        }
        @{
            Name = 'Description'
            Expression = 'if ($data.description) { $data.description } else { ''N/A'' }'
        }
        @{
            Name = 'Address'
            Expression = '$addressLine'
        }
        @{
            Name = 'Country/Region'
            Expression = '$country'
        }
        @{
            Name = 'Label Count'
            Expression = '$labelCount'
        }
        @{
            Name = 'Resource U'
            Expression = '$ResUCount'
        }
    )

    Export = @{
        WorksheetName = 'Arc Sites'
        TableNamePrefix = 'ArcSitesTable_'
        Columns = @(
            'Subscription'
            'Resource Group'
            'Name'
            'Display Name'
            'Description'
            'Address'
            'Country/Region'
            'Label Count'
            'Resource U'
        )
        TagColumns = @()
        TagColumnsBefore = $null
        NumberFormat = '0'
        ConditionalText = @()
    }

    SourceCollector = 'src/collect/ConvertTo-ScoutArcSiteResource.ps1'

    # This definition was hand-authored for AB#6801 against Microsoft.Edge/sites, NOT lifted
    # from a v2 collector -- see this file's header. ConvertTo-ScoutArcSiteResource.ps1 is the
    # REST projection helper the pipeline calls, not a Standard-contract collector, so
    # ConvertTo-ScoutCollectorDefinition.ps1 cannot regenerate a definition from it: it looks
    # for a `$Task -eq 'Processing'` branch that a converter has no reason to contain.
    #
    # The drift check therefore threw on every run and failed the whole gate. It has been red
    # on main since at least the v3.2.0 release, blocking every pull request in the repo --
    # a check nobody can make pass stops being a gate and starts being a tax.
    #
    # Declaring the exemption is the mechanism Test-ScoutCollectorDefinition.ps1 already
    # provides for exactly this case: it prints "Manual conversion drift exemption" with this
    # reason rather than silently skipping, so the exemption stays visible in the log.
    # SourceCollector is retained as provenance for where the projection logic lives.
    ManualConversionReason = 'Hand-authored for AB#6801 against Microsoft.Edge/sites. SourceCollector names the REST projection helper, which is not a Standard-contract collector and cannot be mechanically re-converted; the committed golden output is the behavioural evidence instead.'
}