internal/functions/ConvertTo-AzOpsState.ps1
function ConvertTo-AzOpsState { <# .SYNOPSIS The cmdlet converts Azure resources (Resources/ResourceGroups/Policy/PolicySet/PolicyAssignments/RoleAssignment/Definition) to the AzOps state format and exports them to the file structure. .DESCRIPTION The cmdlet converts Azure resources (Resources/ResourceGroups/Policy/PolicySet/PolicyAssignments/RoleAssignment/Definition) to the AzOps state format and exports them to the file structure. It is normally executed and orchestrated through the Invoke-AzOpsPull cmdlet. As most of the AzOps-cmdlets, it is dependant on the AzOpsAzManagementGroup and AzOpsSubscriptions variables. Cmdlet will look into jq filter is template directory for the specific one before using the generic one at the root of the module .PARAMETER Resource Object with resource as input .PARAMETER ExportPath ExportPath is used if resource needs to be exported to other path than the AzOpsScope path .PARAMETER ReturnObject Used if to return object in pipeline instead of exporting file .PARAMETER ChildResource The ChildResource contains details of the child resource .PARAMETER StatePath The root path to where the entire state is being built in. .EXAMPLE $policy = Get-AzPolicyDefinition -Custom | Select-Object -Last 1 ConvertTo-AzOpsState -Resource $policy Export custom policy definition to the AzOps StatePath .EXAMPLE $policy = Get-AzPolicyDefinition -Custom | Select-Object -Last 1 ConvertTo-AzOpsState -Resource $policy -ReturnObject Name Value ---- ----- $schema http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json# contentVersion 1.0.0.0 parameters {input} Serialize custom policy definition to the AzOps format, return object instead of export file .INPUTS Resource .OUTPUTS Resource in AzOpsState json format or object returned as [PSCustomObject] depending on parameters used #> [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $Resource, [string] $ExportPath, [switch] $ReturnObject, [hashtable] $ChildResource, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $StatePath, [string] $JqTemplatePath = (Get-PSFConfigValue -FullName 'AzOps.Core.JqTemplatePath') ) begin { Write-AzOpsMessage -LogLevel InternalComment -LogString 'ConvertTo-AzOpsState.Starting' } process { if ($ChildResource) { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Processing' -LogStringValues $ChildResource.resourceName $objectFilePath = (New-AzOpsScope -scope $ChildResource.parentResourceId -ChildResource $ChildResource -StatePath $Statepath).statepath $jqJsonTemplate = Get-AzOpsTemplateFile -File "templateChildResource.jq" Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Subscription.ChildResource.Jq.Template' -LogStringValues $jqJsonTemplate $object = ($Resource | ConvertTo-Json -Depth 100 -EnumsAsStrings | jq -r '--sort-keys' | jq -r -f $jqJsonTemplate | ConvertFrom-Json) Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Subscription.ChildResource.Exporting' -LogStringValues $objectFilePath ConvertTo-Json -InputObject $object -Depth 100 -EnumsAsStrings | Set-Content -Path $objectFilePath -Encoding UTF8 -Force return } else { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Processing' -LogStringValues $Resource.id } if (-not $ExportPath) { if ($Resource.Id) { # Handle subscription-only scenarios without managementGroup access if ($Resource -is [Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription]) { $objectFilePath = (New-AzOpsScope -scope "/subscriptions/$($Resource.id)" -StatePath $StatePath).statepath } else { $objectFilePath = (New-AzOpsScope -scope $Resource.id -StatePath $StatePath).statepath } } elseif ($Resource.ResourceId) { $objectFilePath = (New-AzOpsScope -scope $Resource.ResourceId -StatePath $StatePath).statepath } else { Write-AzOpsMessage -LogLevel Error -LogString 'ConvertTo-AzOpsState.NoExportPath' -LogStringValues $Resource.GetType() } } else { $objectFilePath = $ExportPath } # Create folder structure if it doesn't exist if (-not (Test-Path -Path $objectFilePath)) { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.File.Create' -LogStringValues $objectFilePath $null = New-Item -Path $objectFilePath -ItemType "file" -Force } else { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.File.UseExisting' -LogStringValues $objectFilePath } # If export file path ends with parameter $generateTemplateParameter = $objectFilePath.EndsWith('.parameters.json') ? $true : $false Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplateParameter' -LogStringValues "$generateTemplateParameter" $resourceType = $null switch ($Resource) { { $_.ResourceType } { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.ResourceType' -LogStringValues "$($Resource.ResourceType)" $resourceType = $_.ResourceType break } # Management Groups { $_ -is [Microsoft.Azure.Commands.Resources.Models.ManagementGroups.PSManagementGroup] -or $_ -is [Microsoft.Azure.Commands.Resources.Models.ManagementGroups.PSManagementGroupChildInfo] } { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.PSObject' -LogStringValues "$($_.GetType())" if ($_.Type -eq "/subscriptions") { $resourceType = 'Microsoft.Management/managementGroups/subscriptions' break } else { $resourceType = 'Microsoft.Management/managementGroups' break } } # Subscriptions { $_ -is [Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription] } { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.PSObject' -LogStringValues "$($_.GetType())" $resourceType = 'Microsoft.Subscription/subscriptions' if (-not $Resource.Type) { $Resource | Add-Member -NotePropertyName Type -NotePropertyValue $resourceType } break } # Resource Groups { $_ -is [Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroup] } { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.PSObject' -LogStringValues "$($_.GetType())" $resourceType = 'Microsoft.Resources/resourceGroups' break } # Resources - Controlled group for raw objects { $_ -is [Microsoft.Azure.Commands.Profile.Models.PSAzureTenant] } { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.PSObject' -LogStringValues "$($_.GetType())" break } { $_.type } { if ( $_.type -eq 'Microsoft.Resources/subscriptions/resourceGroups') { $resourceType = 'Microsoft.Resources/resourceGroups' } else { $resourceType = $_.type } Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.ResourceType' -LogStringValues $resourceType break } Default { Write-AzOpsMessage -LogLevel Warning -LogString 'ConvertTo-AzOpsState.ObjectType.Resolved.Generic' -LogStringValues "$($_.GetType())" break } } if ($resourceType) { $providerNamespace = ($resourceType -split '/' | Select-Object -First 1) Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ProviderNamespace' -LogStringValues $providerNamespace if (($resourceType -split '/').Count -eq 2) { $resourceTypeName = (($resourceType -split '/', 2) | Select-Object -Last 1) Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ResourceTypeName' -LogStringValues $resourceTypeName $resourceApiTypeName = (($resourceType -split '/', 2) | Select-Object -Last 1) Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ResourceApiTypeName' -LogStringValues $resourceApiTypeName } if (($resourceType -split '/').Count -eq 3) { $resourceTypeName = ((($resourceType -split '/', 3) | Select-Object -Last 2) -join '/') Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ResourceTypeName' -LogStringValues $resourceTypeName $resourceApiTypeName = (($resourceType -split '/', 3) | Select-Object -Index 1) Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ResourceApiTypeName' -LogStringValues $resourceApiTypeName } $jqRemoveTemplate = Get-AzOpsTemplateFile -File (Join-Path $providerNamespace -ChildPath "$resourceTypeName.jq") -Fallback "generic.jq" Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Jq.Remove' -LogStringValues $jqRemoveTemplate # If we were able to determine resourceType, apply filter and write template or template parameter files based on output filename. $object = $Resource | ConvertTo-Json -Depth 100 -EnumsAsStrings | jq -r -f $jqRemoveTemplate | ConvertFrom-Json if ($ReturnObject) { return $object } else { if ($generateTemplateParameter) { #region Generating Template Parameter Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplateParameter' $jqJsonTemplate = Get-AzOpsTemplateFile -File (Join-Path $providerNamespace -ChildPath "$resourceTypeName.parameters.jq") -Fallback "template.parameters.jq" Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Jq.Template' -LogStringValues $jqJsonTemplate $object = ($object | ConvertTo-Json -Depth 100 -EnumsAsStrings | jq -r '--sort-keys' | jq -r -f $jqJsonTemplate | ConvertFrom-Json) #endregion } else { #region Generating Template Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate' -LogStringValues "$true" $jqJsonTemplate = Get-AzOpsTemplateFile -File (Join-Path $providerNamespace -ChildPath "$resourceTypeName.template.jq") -Fallback "template.jq" Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Jq.Template' -LogStringValues $jqJsonTemplate $object = ($object | ConvertTo-Json -Depth 100 -EnumsAsStrings | jq -r '--sort-keys' | jq -r -f $jqJsonTemplate | ConvertFrom-Json) #endregion #region Replace Resource Type and API Version if ( ($Script:AzOpsResourceProvider | Where-Object { $_.ProviderNamespace -eq $providerNamespace }) -and (($Script:AzOpsResourceProvider | Where-Object { $_.ProviderNamespace -eq $providerNamespace }).ResourceTypes | Where-Object { $_.ResourceTypeName -eq $resourceApiTypeName }) ) { $apiVersions = (($Script:AzOpsResourceProvider | Where-Object { $_.ProviderNamespace -eq $providerNamespace }).ResourceTypes | Where-Object { $_.ResourceTypeName -eq $resourceApiTypeName }).ApiVersions # Handle GA/Preview API versions $gaApiVersion = $apiVersions | Where-Object {$_ -match '^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$'} | Sort-Object -Descending $preApiVersion = $apiVersions | Where-Object {$_ -notmatch '^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$'} | Sort-Object -Descending if ($null -eq $gaApiVersion) { $apiVersion = $preApiVersion | Select-Object -First 1 } else { $apiVersion = $gaApiVersion | Select-Object -First 1 } Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ApiVersion' -LogStringValues $resourceType, $apiVersion $object.resources[0].apiVersion = $apiVersion $object.resources[0].type = $resourceType } else { Write-AzOpsMessage -LogLevel Warning -LogString 'ConvertTo-AzOpsState.GenerateTemplate.NoApiVersion' -LogStringValues $resourceType } #endregion #region Append Name for child resource # [Patch] Temporary until mangementGroup() is fully implemented if ($resourceType -eq "Microsoft.Management/managementGroups/subscriptions") { $resourceName = (((New-AzOpsScope -Scope $Resource.Id).ManagementGroup) + "/" + $Resource.Name) $object.resources[0].name = $resourceName Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.GenerateTemplate.ChildResource' -LogStringValues $resourceName } #endregion } Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Exporting' -LogStringValues $objectFilePath ConvertTo-Json -InputObject $object -Depth 100 -EnumsAsStrings | Set-Content -Path ([WildcardPattern]::Escape($objectFilePath)) -Encoding UTF8 -Force } } else { Write-AzOpsMessage -LogLevel Debug -LogString 'ConvertTo-AzOpsState.Exporting.Default' -LogStringValues $objectFilePath if ($ReturnObject) { return $Resource } else { ConvertTo-Json -InputObject $Resource -Depth 100 -EnumsAsStrings | Set-Content -Path ([WildcardPattern]::Escape($objectFilePath)) -Encoding UTF8 -Force } } } } |