From 369c1287f6e4f5cd1ce6274cec9dee301aac7363 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Fri, 23 Apr 2021 13:22:29 -0400 Subject: [PATCH 1/3] Decompile reference() functions with resource name references --- .../Working/issue2380/main.bicep | 32 ++ .../Working/issue2380/main.json | 60 ++++ .../Working/keysinproperties/main.bicep | 299 +++++++++--------- .../Working/resourceIds/main.bicep | 98 +++--- src/Bicep.Decompiler/TemplateConverter.cs | 19 +- 5 files changed, 304 insertions(+), 204 deletions(-) create mode 100644 src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.bicep create mode 100644 src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.json diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.bicep b/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.bicep new file mode 100644 index 00000000000..62df6ee8c9e --- /dev/null +++ b/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.bicep @@ -0,0 +1,32 @@ +targetScope = 'managementGroup' + +@description('EnrollmentAccount used for subscription billing') +param enrollmentAccount string + +@description('BillingAccount used for subscription billing') +param billingAccount string + +@description('Alias to assign to the subscription') +param subscriptionAlias string + +@description('Display name for the subscription') +param subscriptionDisplayName string + +@allowed([ + 'Production' + 'DevTest' +]) +@description('Workload type for the subscription') +param subscriptionWorkload string = 'Production' + +resource subscriptionAlias_resource 'Microsoft.Subscription/aliases@2020-09-01' = { +//@[82:333) [BCP135 (Error)] Scope "managementGroup" is not valid for this resource type. Permitted scopes: "tenant". |{\n name: subscriptionAlias\n properties: {\n workload: subscriptionWorkload\n displayName: subscriptionDisplayName\n billingScope: tenantResourceId('Microsoft.Billing/billingAccounts/enrollmentAccounts', billingAccount, enrollmentAccount)\n }\n}| + name: subscriptionAlias + properties: { + workload: subscriptionWorkload + displayName: subscriptionDisplayName + billingScope: tenantResourceId('Microsoft.Billing/billingAccounts/enrollmentAccounts', billingAccount, enrollmentAccount) + } +} + +output subscriptionId string = subscriptionAlias_resource.properties.subscriptionId diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.json b/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.json new file mode 100644 index 00000000000..809eb4fd4f1 --- /dev/null +++ b/src/Bicep.Decompiler.IntegrationTests/Working/issue2380/main.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "enrollmentAccount": { + "type": "string", + "metadata": { + "description": "EnrollmentAccount used for subscription billing" + } + }, + "billingAccount": { + "type": "string", + "metadata": { + "description": "BillingAccount used for subscription billing" + } + }, + "subscriptionAlias": { + "type": "string", + "metadata": { + "description": "Alias to assign to the subscription" + } + }, + "subscriptionDisplayName": { + "type": "string", + "metadata": { + "description": "Display name for the subscription" + } + }, + "subscriptionWorkload": { + "type": "string", + "defaultValue": "Production", + "allowedValues": [ + "Production", + "DevTest" + ], + "metadata": { + "description": "Workload type for the subscription" + } + } + }, + "resources": [ + { + //"scope": "/", + "name": "[parameters('subscriptionAlias')]", + "type": "Microsoft.Subscription/aliases", + "apiVersion": "2020-09-01", + "properties": { + "workload": "[parameters('subscriptionWorkload')]", + "displayName": "[parameters('subscriptionDisplayName')]", + "billingScope": "[tenantResourceId('Microsoft.Billing/billingAccounts/enrollmentAccounts', parameters('billingAccount'), parameters('enrollmentAccount'))]" + } + } + ], + "outputs": { + "subscriptionId": { + "type": "string", + "value": "[reference(parameters('subscriptionAlias')).subscriptionId]" + } + } +} \ No newline at end of file diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/keysinproperties/main.bicep b/src/Bicep.Decompiler.IntegrationTests/Working/keysinproperties/main.bicep index e1343e7524b..2a9871c9f3f 100644 --- a/src/Bicep.Decompiler.IntegrationTests/Working/keysinproperties/main.bicep +++ b/src/Bicep.Decompiler.IntegrationTests/Working/keysinproperties/main.bicep @@ -1,151 +1,148 @@ -@allowed([ - 'Standard_LRS' - 'Standard_GRS' -]) -@description('Storage account type') -param storageAccountType string = 'Standard_LRS' - -@description('Name of file share to be created') -param fileShareName string = 'sftpfileshare' - -@description('Username to use for SFTP access') -param sftpUser string - -@description('Password to use for SFTP access') -@secure() -param sftpPassword string - -@description('Primary location for resources') -param location string = resourceGroup().location - -var scriptName_var = 'createFileShare' -var identityName_var = 'scratch' -var roleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') -var roleDefinitionName_var = guid(identityName_var, roleDefinitionId) -var sftpContainerName = 'sftp' -var sftpContainerGroupName_var = 'sftp-group' -var sftpContainerImage = 'atmoz/sftp:latest' -var sftpEnvVariable = '${sftpUser}:${sftpPassword}:1001' -var storageAccountName_var = 'sftpstg${uniqueString(resourceGroup().id)}' - -resource identityName 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { - name: identityName_var - location: location -} - -resource roleDefinitionName 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { - name: roleDefinitionName_var - properties: { - roleDefinitionId: roleDefinitionId - principalId: reference(identityName_var).principalId - scope: resourceGroup().id -//@[4:9) [BCP073 (Warning)] The property "scope" is read-only. Expressions cannot be assigned to read-only properties. |scope| - principalType: 'ServicePrincipal' - } - dependsOn: [ - identityName - ] -} - -resource storageAccountName 'Microsoft.Storage/storageAccounts@2019-06-01' = { - name: storageAccountName_var - location: location - sku: { - name: storageAccountType - } - kind: 'StorageV2' - properties: {} - dependsOn: [ - roleDefinitionName - ] -} - -resource scriptName 'Microsoft.Resources/deploymentScripts@2019-10-01-preview' = { - name: scriptName_var - location: location - kind: 'AzurePowerShell' - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${identityName.id}': {} - } - } - properties: { - forceUpdateTag: '1' - azPowerShellVersion: '3.0' - arguments: ' -storageAccountName ${storageAccountName_var} -fileShareName ${fileShareName} -resourceGroupName ${resourceGroup().name}' - scriptContent: '\n param(\n [string] $storageAccountName,\n [string] $fileShareName,\n [string] $resourceGroupName\n )\n Get-AzStorageAccount -StorageAccountName $storageAccountName -ResourceGroupName $resourceGroupName | New-AzStorageShare -Name $fileShareName\n ' - timeout: 'PT5M' - cleanupPreference: 'OnSuccess' - retentionInterval: 'P1D' - } - dependsOn: [ - storageAccountName - ] -} - -resource sftpContainerGroupName 'Microsoft.ContainerInstance/containerGroups@2019-12-01' = { - name: sftpContainerGroupName_var - location: location - properties: { - containers: [ - { - name: sftpContainerName - properties: { - image: sftpContainerImage - environmentVariables: [ - { - name: 'SFTP_USERS' - value: sftpEnvVariable - } - ] - resources: { - requests: { - cpu: 2 - memoryInGB: 1 - } - } - ports: [ - { - port: 22 - } - ] - volumeMounts: [ - { - mountPath: '/home/${sftpUser}/upload' - name: 'sftpvolume' - readOnly: false - } - ] - } - } - ] - osType: 'Linux' - ipAddress: { - type: 'Public' - ports: [ - { - protocol: 'TCP' - port: 22 - } - ] - } - restartPolicy: 'OnFailure' - volumes: [ - { - name: 'sftpvolume' - azureFile: { - readOnly: false - shareName: fileShareName - storageAccountName: storageAccountName_var - storageAccountKey: listKeys(storageAccountName_var, '2018-02-01').keys[0].value - } - } - ] - } - dependsOn: [ - scriptName - ] -} - -output containerIPv4Address string = sftpContainerGroupName.properties.ipAddress.ip +@allowed([ + 'Standard_LRS' + 'Standard_GRS' +]) +@description('Storage account type') +param storageAccountType string = 'Standard_LRS' + +@description('Name of file share to be created') +param fileShareName string = 'sftpfileshare' + +@description('Username to use for SFTP access') +param sftpUser string + +@description('Password to use for SFTP access') +@secure() +param sftpPassword string + +@description('Primary location for resources') +param location string = resourceGroup().location + +var scriptName_var = 'createFileShare' +var identityName_var = 'scratch' +var roleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') +var roleDefinitionName_var = guid(identityName_var, roleDefinitionId) +var sftpContainerName = 'sftp' +var sftpContainerGroupName_var = 'sftp-group' +var sftpContainerImage = 'atmoz/sftp:latest' +var sftpEnvVariable = '${sftpUser}:${sftpPassword}:1001' +var storageAccountName_var = 'sftpstg${uniqueString(resourceGroup().id)}' + +resource identityName 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: identityName_var + location: location +} + +resource roleDefinitionName 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: roleDefinitionName_var + properties: { + roleDefinitionId: roleDefinitionId + principalId: identityName.properties.principalId + scope: resourceGroup().id +//@[4:9) [BCP073 (Warning)] The property "scope" is read-only. Expressions cannot be assigned to read-only properties. |scope| + principalType: 'ServicePrincipal' + } +} + +resource storageAccountName 'Microsoft.Storage/storageAccounts@2019-06-01' = { + name: storageAccountName_var + location: location + sku: { + name: storageAccountType + } + kind: 'StorageV2' + properties: {} + dependsOn: [ + roleDefinitionName + ] +} + +resource scriptName 'Microsoft.Resources/deploymentScripts@2019-10-01-preview' = { + name: scriptName_var + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identityName.id}': {} + } + } + properties: { + forceUpdateTag: '1' + azPowerShellVersion: '3.0' + arguments: ' -storageAccountName ${storageAccountName_var} -fileShareName ${fileShareName} -resourceGroupName ${resourceGroup().name}' + scriptContent: '\n param(\n [string] $storageAccountName,\n [string] $fileShareName,\n [string] $resourceGroupName\n )\n Get-AzStorageAccount -StorageAccountName $storageAccountName -ResourceGroupName $resourceGroupName | New-AzStorageShare -Name $fileShareName\n ' + timeout: 'PT5M' + cleanupPreference: 'OnSuccess' + retentionInterval: 'P1D' + } + dependsOn: [ + storageAccountName + ] +} + +resource sftpContainerGroupName 'Microsoft.ContainerInstance/containerGroups@2019-12-01' = { + name: sftpContainerGroupName_var + location: location + properties: { + containers: [ + { + name: sftpContainerName + properties: { + image: sftpContainerImage + environmentVariables: [ + { + name: 'SFTP_USERS' + value: sftpEnvVariable + } + ] + resources: { + requests: { + cpu: 2 + memoryInGB: 1 + } + } + ports: [ + { + port: 22 + } + ] + volumeMounts: [ + { + mountPath: '/home/${sftpUser}/upload' + name: 'sftpvolume' + readOnly: false + } + ] + } + } + ] + osType: 'Linux' + ipAddress: { + type: 'Public' + ports: [ + { + protocol: 'TCP' + port: 22 + } + ] + } + restartPolicy: 'OnFailure' + volumes: [ + { + name: 'sftpvolume' + azureFile: { + readOnly: false + shareName: fileShareName + storageAccountName: storageAccountName_var + storageAccountKey: listKeys(storageAccountName_var, '2018-02-01').keys[0].value + } + } + ] + } + dependsOn: [ + scriptName + ] +} + +output containerIPv4Address string = sftpContainerGroupName.properties.ipAddress.ip diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/resourceIds/main.bicep b/src/Bicep.Decompiler.IntegrationTests/Working/resourceIds/main.bicep index a2ccc2ae603..c5e699c6c71 100644 --- a/src/Bicep.Decompiler.IntegrationTests/Working/resourceIds/main.bicep +++ b/src/Bicep.Decompiler.IntegrationTests/Working/resourceIds/main.bicep @@ -1,49 +1,49 @@ -@description('Location for all resources.') -param location string = resourceGroup().location - -var fooName = 'Foo!' - -resource fooName_bar 'Foo.Rp/bar@2019-06-01' = { -//@[21:44) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| - name: '${fooName}bar' - location: location - properties: { - foo: 'bar' - } -} - -resource fooName_baz 'Foo.Rp/bar@2019-06-01' = { -//@[21:44) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| - name: '${fooName}baz' - location: location - dependsOn: [ - fooName_bar - ] -} - -resource fooName_blah 'Foo.Rp/bar@2019-06-01' = { -//@[22:45) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| - name: '${fooName}blah' - location: location - dependsOn: [ - fooName_bar - ] -} - -resource fooName_blah2 'Foo.Rp/bar@2019-06-01' = { -//@[23:46) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| - name: '${fooName}blah2' - location: location - properties: { - foobar: reference('${fooName}bar').foo - foobarFull: reference('${fooName}bar', '2019-06-01', 'Full').properties.foo - foobarLocation: reference('${fooName}bar', '2019-06-01', 'Full').location - foobarResId: fooName_bar.properties.foo - foobarResIdFull: reference(fooName_bar.id, '2019-06-01', 'Full').properties.foo - foobarResIdLocation: reference(fooName_bar.id, '2019-06-01', 'Full').location - } - dependsOn: [ - 'Foo.Rp/bar${fooName}bar' -//@[4:29) [BCP034 (Error)] The enclosing array expected an item of type "module[] | (resource | module) | resource[]", but the provided item was of type "string". |'Foo.Rp/bar${fooName}bar'| - ] -} +@description('Location for all resources.') +param location string = resourceGroup().location + +var fooName = 'Foo!' + +resource fooName_bar 'Foo.Rp/bar@2019-06-01' = { +//@[21:44) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| + name: '${fooName}bar' + location: location + properties: { + foo: 'bar' + } +} + +resource fooName_baz 'Foo.Rp/bar@2019-06-01' = { +//@[21:44) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| + name: '${fooName}baz' + location: location + dependsOn: [ + fooName_bar + ] +} + +resource fooName_blah 'Foo.Rp/bar@2019-06-01' = { +//@[22:45) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| + name: '${fooName}blah' + location: location + dependsOn: [ + fooName_bar + ] +} + +resource fooName_blah2 'Foo.Rp/bar@2019-06-01' = { +//@[23:46) [BCP081 (Warning)] Resource type "Foo.Rp/bar@2019-06-01" does not have types available. |'Foo.Rp/bar@2019-06-01'| + name: '${fooName}blah2' + location: location + properties: { + foobar: fooName_bar.properties.foo + foobarFull: reference('${fooName}bar', '2019-06-01', 'Full').properties.foo + foobarLocation: reference('${fooName}bar', '2019-06-01', 'Full').location + foobarResId: fooName_bar.properties.foo + foobarResIdFull: reference(fooName_bar.id, '2019-06-01', 'Full').properties.foo + foobarResIdLocation: reference(fooName_bar.id, '2019-06-01', 'Full').location + } + dependsOn: [ + 'Foo.Rp/bar${fooName}bar' +//@[4:29) [BCP034 (Error)] The enclosing array expected an item of type "module[] | (resource | module) | resource[]", but the provided item was of type "string". |'Foo.Rp/bar${fooName}bar'| + ] +} diff --git a/src/Bicep.Decompiler/TemplateConverter.cs b/src/Bicep.Decompiler/TemplateConverter.cs index f24ffddc77c..68fc7e324c1 100644 --- a/src/Bicep.Decompiler/TemplateConverter.cs +++ b/src/Bicep.Decompiler/TemplateConverter.cs @@ -410,10 +410,21 @@ private SyntaxBase ParseFunctionExpression(FunctionExpression expression) { if (expression.Parameters.Length == 1 && expression.Parameters[0] is FunctionExpression resourceIdExpression && resourceIdExpression.NameEquals("resourceid")) { - // resourceid directly inside a reference - check if it's a reference to a known resource - var resourceName = TryLookupResource(resourceIdExpression); - - if (resourceName != null) + // reference(resourceId(<...>)) + // check if it's a reference to a known resource + if (TryLookupResource(expression.Parameters[0]) is {} resourceName) + { + baseSyntax = new PropertyAccessSyntax( + new VariableAccessSyntax(SyntaxFactory.CreateIdentifier(resourceName)), + SyntaxFactory.DotToken, + SyntaxFactory.CreateIdentifier("properties")); + } + } + else if (expression.Parameters.Length == 1) + { + // reference() + // let's try looking the name up directly + if (TryLookupResource(expression.Parameters[0]) is {} resourceName) { baseSyntax = new PropertyAccessSyntax( new VariableAccessSyntax(SyntaxFactory.CreateIdentifier(resourceName)), From 909843fe310bbbd9759045254284bd0d7389a516 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Fri, 23 Apr 2021 14:27:30 -0400 Subject: [PATCH 2/3] Fix decompiler to use LF for newlines --- src/Bicep.Decompiler/TemplateDecompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bicep.Decompiler/TemplateDecompiler.cs b/src/Bicep.Decompiler/TemplateDecompiler.cs index 47db00f9558..73d23a11b44 100644 --- a/src/Bicep.Decompiler/TemplateDecompiler.cs +++ b/src/Bicep.Decompiler/TemplateDecompiler.cs @@ -90,7 +90,7 @@ private static ImmutableDictionary PrintFiles(Workspace workspace) var filesToSave = new Dictionary(); foreach (var (fileUri, syntaxTree) in workspace.GetActiveSyntaxTrees()) { - filesToSave[fileUri] = PrettyPrinter.PrintProgram(syntaxTree.ProgramSyntax, new PrettyPrintOptions(NewlineOption.Auto, IndentKindOption.Space, 2, false)); + filesToSave[fileUri] = PrettyPrinter.PrintProgram(syntaxTree.ProgramSyntax, new PrettyPrintOptions(NewlineOption.LF, IndentKindOption.Space, 2, false)); } return filesToSave.ToImmutableDictionary(); From 9f4dbaec3d751d9b5f79bb2a10e953f2aad1c2be Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Fri, 23 Apr 2021 15:07:45 -0400 Subject: [PATCH 3/3] Fix test for LF --- src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs b/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs index f9d4a1858a9..a80e6e3d095 100644 --- a/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs +++ b/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs @@ -107,7 +107,7 @@ public void Decompiler_generates_expected_bicep_files_with_diagnostics(ExampleDa var diagnostics = diagnosticsBySyntaxTree[syntaxTree]; var bicepOutput = filesToSave[syntaxTree.FileUri]; - var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(bicepOutput, Environment.NewLine, diagnostics, diag => OutputHelper.GetDiagLoggingString(bicepOutput, outputDirectory, diag)); + var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(bicepOutput, "\n", diagnostics, diag => OutputHelper.GetDiagLoggingString(bicepOutput, outputDirectory, diag)); File.WriteAllText(syntaxTree.FileUri.LocalPath + ".actual", sourceTextWithDiags); sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput(