public/Get-AzureApiSpecsVersionList.ps1
<#
.SYNOPSIS Get an overview of all API Versions for any existing ProviderNamespace/ResourceType combination .DESCRIPTION Get an overview of all API Versions for any existing ProviderNamespace/ResourceType combination .PARAMETER IncludePreview Optional. Provide if preview versions should be included .PARAMETER KeepArtifacts Optional. Provide if any downloaded data should not be removed after the function ran. This is useful to speed up subsequent runs. .PARAMETER IncludeExternalSources Optional. A flag to control whether that found API versions (based on this repository) should be enriched with other sources like those returned by the `Get-AzResourceProvider -ListAvailable` function. This is usually benefitial to get a complete picture as it can happen that either are missing individual versions. NOTE: This function requires that the `Az.Resources` module is installed. .EXAMPLE Get-AzureAPISpecsVersionList Returns an object like: { "Microsoft.AAD": { "domainServices": [ "2017-01-01", "2017-06-01", "2020-01-01", "2021-03-01", "2021-05-01", "2022-09-01" ], "domainServices/ouContainer": [ "2017-06-01", "2020-01-01", "2021-03-01", "2021-05-01", "2022-09-01" ] }, (...) } #> function Get-AzureAPISpecsVersionList { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [switch] $IncludePreview, [Parameter(Mandatory = $false)] [switch] $KeepArtifacts, [Parameter(Mandatory = $false)] [switch] $IncludeExternalSources ) begin { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) } process { ######################################### ## Temp Clone API Specs Repository ## ######################################### $repoUrl = $script:CONFIG.url_CloneRESTAPISpecRepository $repoName = Split-Path $repoUrl -LeafBase $specificationPath = Join-Path (Join-Path $script:temp $repoName) 'specification' Copy-CustomRepository -RepoUrl $repoUrl -RepoName $repoName try { ############################## ## Collect API Versions ## ############################## $relevantFolderList = Get-FolderList -RootFolder $specificationPath $filePaths = $relevantFolderList | Foreach-Object { (Get-ChildItem -Path $_ -Recurse -Filter '*.json').FullName } | Where-Object { (Split-Path $_ -Leaf) -notin @('common.json', 'privatelinks.json') -and ($_ -replace '\\', '/') -notlike "*/examples/*" } if (-not $IncludePreview) { $beforeCount = $filePaths.Count $filePaths = $filePaths | Where-Object { ($_ -replace '\\', '/') -notlike "*/preview/*" } Write-Verbose ("Filtered [{0}] files for preview versions out" -f ($beforeCount - $filePaths.Count)) } Write-Verbose ("Found [{0}] files to analyze" -f $filePaths.Count) $resultSet = @{} for ($fileIndex = 0; $fileIndex -lt $filePaths.Count; $fileIndex++) { $filePath = $filePaths[$fileIndex] $apiVersion = Split-Path (Split-Path $filePath -Parent) -Leaf $specificationData = Get-Content $filePath -Raw | ConvertFrom-Json -AsHashtable $pathData = $specificationData.paths if (-not $pathData) { continue } # Collect all URL paths that # - contain a 'PUT' - which means you can create the resource # - end with a variable name {...} or a string (i.e. given value like "default") # - contain the 'Microsoft.' provider, or equals any of the exceptions like ResourceGroup & ResourceTags $relevantPaths = $pathData.Keys | Where-Object { $pathData[$_].Keys -contains 'put' -and $_ -match "\w+}$|\w+$" -and ($_ -match ".*\/providers\/Microsoft\..+" -or # Ignoring anything without 'providers' in the name as it does not seem to be relevant to IaC $_ -in @( '/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}', # Special case: Resource Group '/{scope}/providers/Microsoft.Resources/tags/default' # Special case: Tags )) } if (-not $relevantPaths) { continue } foreach ($relevantPath in $relevantPaths) { if ($relevantPath -eq '/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}') { # Special case: Resource Group $providerNamespace = 'Microsoft.Resources' $resourceType = 'resourceGroups' } else { $identifierElem = ($relevantPath -split '\/providers\/')[-1] -split '\/' $providerNamespace = $identifierElem[0] # E.g. Microsoft.Storage # Add the remaining elements (every 2nd as everything in between represents a 'name') $remainingRelevantElem = $identifierElem[1..($identifierElem.Count)] $resourceType = '' for ($index = 0; $index -lt $remainingRelevantElem.Count; $index++) { if ($index % 2 -eq 0) { $resourceType += ('/{0}' -f $remainingRelevantElem[$index]) } } $resourceType = $resourceType.TrimStart('/') } if ($resultSet.Keys -notcontains $providerNamespace) { $resultSet[$providerNamespace] = @{} } if ($relevantPath -match ".+}\/{(\w+)}\/{\w+}$") { # Special resource types like PrivateDNSZones # '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/dnszones/{zoneName}/{recordType}/{relativeRecordSetName}' # $pathElemName = ($resourceType -split '/')[-2] -replace '\{|}' $pathElemName = $matches[1] # Needs special handling as the 'recordType' in this example must be resolved with a parameter that specifies to be part of the path like ["in": "path"] # 1. check: Direct URL Parameter $pathParameters = $pathData[$relevantPath].put.parameters | Where-Object { $_.in -eq 'path' -and $_.name -eq $pathElemName } if (-not $pathParameters) { continue } # 2. check: Parameters-specification element - Only relevant for non-deployment types? # if (-not $pathParameters) { # $pathParameters = $specificationData.parameters.Keys | Where-Object { # $specificationData.parameters[$_].in -eq 'path' # } | ForEach-Object { # $specificationData.parameters[$_] # } # } if ($pathParameters.Keys -contains 'enum') { foreach ($elem in $pathParameters.enum) { $formattedResourceType = $resourceType -replace "\{$pathElemName}", $elem if ($resultSet[$providerNamespace].Keys -notcontains $formattedResourceType) { $resultSet[$providerNamespace][$formattedResourceType] = @() } $apiVersionList = (@() + $resultSet[$providerNamespace][$formattedResourceType] + $apiVersion) | Sort-Object -Unique $resultSet[$providerNamespace][$formattedResourceType] = $apiVersionList -is [array] ? $apiVersionList : @($apiVersion) } } else { Write-Verbose "Ignoring [$relevantPath] in file [$filePath] as we failed to resolve the path's properties." } } else { if ($resultSet[$providerNamespace].Keys -notcontains $resourceType) { $resultSet[$providerNamespace][$resourceType] = @() } $apiVersionList = (@() + $resultSet[$providerNamespace][$resourceType] + $apiVersion) | Sort-Object -Unique $resultSet[$providerNamespace][$resourceType] = $apiVersionList -is [array] ? $apiVersionList : @($apiVersion) } } $percentageComplete = [System.Math]::Floor(($fileIndex / $filePaths.Count) * 100) Write-Progress -Activity "Analyzing in progress" -Status ("[{0}/{1}] or {2}% files processed" -f $fileIndex, $filePaths.count, $percentageComplete) -PercentComplete $percentageComplete } # Add other sources if ($IncludeExternalSources) { # Further expand on the known API versions with those returned by the official PowerShell function $PSApiVersions = Get-AzResourceProvider -ListAvailable foreach ($providerNamespaceBlock in $PSApiVersions) { $providerNamespace = $providerNamespaceBlock.ProviderNamespace if ($resultSet.Keys -notcontains $providerNamespace) { $resultSet[$providerNamespace] = @{} } foreach ($resourceTypeBlock in $providerNamespaceBlock.ResourceTypes) { $resourceType = $resourceTypeBlock.ResourceTypeName if ($resultSet[$providerNamespace].Keys -notcontains $resourceType) { $resultSet[$providerNamespace][$resourceType] = @() } if ($IncludePreview) { $apiVersions = $resourceTypeBlock.ApiVersions } else { $apiVersions = $resourceTypeBlock.ApiVersions | Where-Object { $_ -notlike "*preview*" } } $apiVersionList = (@() + $resultSet[$providerNamespace][$resourceType] + $apiVersions) | Sort-Object -Unique $resultSet[$providerNamespace][$resourceType] = $apiVersionList -is [array] ? $apiVersionList : @($apiVersions) } } } # Sort result $orderedResultSet = [ordered]@{} foreach ($providerNamespace in ($resultSet.Keys | Sort-Object)) { if ($orderedResultSet.Keys -notcontains $providerNamespace) { $orderedResultSet[$providerNamespace] = [ordered]@{} } foreach ($resourceType in ($resultSet[$providerNamespace].Keys | Sort-Object)) { $orderedResultSet[$providerNamespace][$resourceType] = $resultSet[$providerNamespace][$resourceType] } } return $orderedResultSet } catch { throw ($_, $_.ScriptStackTrace) } finally { ########################## ## Remove Artifacts ## ########################## if (-not $KeepArtifacts) { Write-Verbose ('Deleting temp folder [{0}]' -f $script:temp) $null = Remove-Item $script:temp -Recurse -Force } } } end { Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) } } |