Private/Read-AACCosmosDBResource.ps1

function Read-AACCosmosDBResource {
    <#
    .SYNOPSIS
        Reads what Invoke-AACCosmosDBAssessment needs from each Cosmos DB
        account through Resource Manager - its databases and containers (in
        the account's API), their throughput, and where its logs go - many
        requests at once.
    .DESCRIPTION
        Each API names its levels differently:
          NoSQL sqlDatabases / containers
          MongoDB mongodbDatabases / collections
          Cassandra cassandraKeyspaces / tables
          Gremlin gremlinDatabases / graphs
          Table tables (no database level)
        Three rounds through Invoke-AACArmParallel (Reader is enough):
          1. per account: the diagnostic settings, and its databases (or,
             Table, its tables)
          2. per database: its containers, and the database's shared
             throughput
          3. per container: its own throughput
        Throughput isn't read for serverless accounts (they have none). A
        throughputSettings answer of 404 or 400 means none at that level.
 
        -Account: @(@{ Id; Api (NoSQL, MongoDB, Cassandra, Gremlin, Table);
        Serverless }). Returns @{ account id = @{ Logs (the diagnostic
        settings' result); Error (why the databases couldn't be read);
        Databases = @(@{ Name; Id; Throughput; Containers = @(@{ Name; Id;
        Resource (the container's resource properties); Throughput }) }) } }
        - Throughput being Invoke-AACArmParallel's result, or $null.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]] $Account,

        [ValidateRange(1, 32)]
        [int] $ThrottleLimit = 12,

        [scriptblock] $OnProgress
    )

    $api = 'api-version=2024-05-15'
    $levels = @{ NoSQL = @('sqlDatabases', 'containers'); MongoDB = @('mongodbDatabases', 'collections'); Cassandra = @('cassandraKeyspaces', 'tables'); Gremlin = @('gremlinDatabases', 'graphs'); Table = @('', 'tables') }
    $run = { param([System.Collections.IDictionary] $Uris) if ($Uris.Count) { Invoke-AACArmParallel -Uri @($Uris.Values) -ThrottleLimit $ThrottleLimit -OnProgress $OnProgress } else { @{} } }
    $itemsOf = { param($Answer) @(if ($Answer -and -not $Answer['Error']) { $Answer['Items'] | Where-Object { $_ } }) }

    $out = @{}
    $byId = @{}
    $first = [ordered]@{}
    foreach ($a in $Account) {
        $out[$a.Id] = @{ Logs = $null; Error = ''; Databases = [System.Collections.Generic.List[hashtable]]::new() }
        $byId[$a.Id] = $a
        $level = $levels[[string]$a.Api]
        $first["$($a.Id)|Logs"] = "$($a.Id)/providers/Microsoft.Insights/diagnosticSettings?api-version=2021-05-01-preview"
        $first["$($a.Id)|List"] = if ($level[0]) { "$($a.Id)/$($level[0])?$api" } else { "$($a.Id)/$($level[1])?$api" }
    }
    $answers = & $run $first

    # Round 2: each database's containers and shared throughput - or, for Table, each table's throughput.
    $second = [ordered]@{}
    foreach ($a in $Account) {
        $out[$a.Id].Logs = $answers[$first["$($a.Id)|Logs"]]
        $list = $answers[$first["$($a.Id)|List"]]
        if (-not $list -or $list['Error']) { $out[$a.Id].Error = $(if ($list) { [string]$list['Error'] } else { 'No answer.' }); continue }
        $level = $levels[[string]$a.Api]
        if (-not $level[0]) {
            # Table: one implicit database holding the tables.
            $database = @{ Name = ''; Id = $a.Id; Throughput = $null; Containers = [System.Collections.Generic.List[hashtable]]::new() }
            foreach ($t in (& $itemsOf $list)) {
                $container = @{ Name = [string]$t['name']; Id = [string]$t['id']; Resource = $t['properties']; Throughput = $null }
                $database.Containers.Add($container)
                if (-not $a.Serverless) { $second["$($a.Id)|$($container.Id)|ct"] = "$($container.Id)/throughputSettings/default?$api" }
            }
            $out[$a.Id].Databases.Add($database)
            continue
        }
        foreach ($d in (& $itemsOf $list)) {
            $database = @{ Name = [string]$d['name']; Id = [string]$d['id']; Throughput = $null; Containers = [System.Collections.Generic.List[hashtable]]::new() }
            $out[$a.Id].Databases.Add($database)
            $second["$($a.Id)|$($database.Id)|children"] = "$($database.Id)/$($level[1])?$api"
            if (-not $a.Serverless) { $second["$($a.Id)|$($database.Id)|dt"] = "$($database.Id)/throughputSettings/default?$api" }
        }
    }
    $answers = & $run $second
    $third = [ordered]@{}
    foreach ($key in $second.Keys) {
        $accountId, $resourceId, $what = $key -split '\|', 3
        $answer = $answers[$second[$key]]
        $databases = $out[$accountId].Databases
        switch ($what) {
            'ct' { foreach ($db in $databases) { foreach ($c in $db.Containers) { if ($c.Id -eq $resourceId) { $c.Throughput = $answer } } } }
            'dt' { foreach ($db in $databases) { if ($db.Id -eq $resourceId) { $db.Throughput = $answer } } }
            'children' {
                foreach ($db in $databases) {
                    if ($db.Id -ne $resourceId) { continue }
                    foreach ($c in (& $itemsOf $answer)) {
                        $container = @{ Name = [string]$c['name']; Id = [string]$c['id']; Resource = $c['properties']; Throughput = $null }
                        $db.Containers.Add($container)
                        if (-not $byId[$accountId].Serverless) { $third["$accountId|$($container.Id)"] = "$($container.Id)/throughputSettings/default?$api" }
                    }
                }
            }
        }
    }

    # Round 3: each container's own throughput.
    $answers = & $run $third
    foreach ($key in $third.Keys) {
        $accountId, $containerId = $key -split '\|', 2
        foreach ($db in $out[$accountId].Databases) { foreach ($c in $db.Containers) { if ($c.Id -eq $containerId) { $c.Throughput = $answers[$third[$key]] } } }
    }
    $out
}