diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.bicep b/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.bicep new file mode 100644 index 00000000000..02787eefbe4 --- /dev/null +++ b/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.bicep @@ -0,0 +1,40 @@ +param hostingPlanName string +param location string = resourceGroup().location + +var siteName_var = 'ExampleSite${uniqueString(resourceGroup().id)}' + +resource hostingPlanName_resource 'Microsoft.Web/serverfarms@2020-06-01' = { + name: hostingPlanName + location: location + sku: { + tier: 'Free' + name: 'f1' + capacity: 0 + } + properties: { + targetWorkerCount: 1 + } +} + +resource siteName 'Microsoft.Web/sites@2020-06-01' = { + name: siteName_var + location: location + properties: { + serverFarmId: hostingPlanName + } + dependsOn: [ + hostingPlanName_resource + ] +} + +resource siteLock 'Microsoft.Authorization/locks@2016-09-01' = { + name: 'siteLock' + properties: { + level: 'CanNotDelete' + notes: 'Site should not be deleted.' + } + scope: siteName + dependsOn: [ + siteName + ] +} diff --git a/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.json b/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.json new file mode 100644 index 00000000000..c6bb8db0ec6 --- /dev/null +++ b/src/Bicep.Decompiler.IntegrationTests/Working/extensionresource/main.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "hostingPlanName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + } + }, + "variables": { + "siteName": "[concat('ExampleSite', uniqueString(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2020-06-01", + "name": "[parameters('hostingPlanName')]", + "location": "[parameters('location')]", + "sku": { + "tier": "Free", + "name": "f1", + "capacity": 0 + }, + "properties": { + "targetWorkerCount": 1 + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2020-06-01", + "name": "[variables('siteName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" + ], + "properties": { + "serverFarmId": "[parameters('hostingPlanName')]" + } + }, + { + "type": "Microsoft.Authorization/locks", + "apiVersion": "2016-09-01", + "name": "siteLock", + "scope": "[concat('Microsoft.Web/sites/', variables('siteName'))]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('siteName'))]" + ], + "properties": { + "level": "CanNotDelete", + "notes": "Site should not be deleted." + } + } + ] +} \ No newline at end of file diff --git a/src/Bicep.Decompiler/TemplateConverter.cs b/src/Bicep.Decompiler/TemplateConverter.cs index 87a20876f03..eaf8404df85 100644 --- a/src/Bicep.Decompiler/TemplateConverter.cs +++ b/src/Bicep.Decompiler/TemplateConverter.cs @@ -639,28 +639,28 @@ StringSyntax createFakeModulePath(string templateLinkExpression) return SyntaxHelpers.CreateStringLiteral(filePath); } - private SyntaxBase? ProcessCondition(JObject resource) - { - JProperty? conditionProperty = TemplateHelpers.GetProperty(resource, "condition"); - - if (conditionProperty == null) - { - return null; - } - - SyntaxBase conditionExpression = ParseJToken(conditionProperty.Value); - - if (conditionExpression is not ParenthesizedExpressionSyntax) - { - conditionExpression = new ParenthesizedExpressionSyntax( - SyntaxHelpers.CreateToken(TokenType.LeftParen, "("), - conditionExpression, - SyntaxHelpers.CreateToken(TokenType.RightParen, ")")); - } - - return new IfConditionSyntax( - SyntaxHelpers.CreateToken(TokenType.Identifier, "if"), - conditionExpression); + private SyntaxBase? ProcessCondition(JObject resource) + { + JProperty? conditionProperty = TemplateHelpers.GetProperty(resource, "condition"); + + if (conditionProperty == null) + { + return null; + } + + SyntaxBase conditionExpression = ParseJToken(conditionProperty.Value); + + if (conditionExpression is not ParenthesizedExpressionSyntax) + { + conditionExpression = new ParenthesizedExpressionSyntax( + SyntaxHelpers.CreateToken(TokenType.LeftParen, "("), + conditionExpression, + SyntaxHelpers.CreateToken(TokenType.RightParen, ")")); + } + + return new IfConditionSyntax( + SyntaxHelpers.CreateToken(TokenType.Identifier, "if"), + conditionExpression); } private SyntaxBase? ProcessDependsOn(JObject resource) @@ -707,7 +707,7 @@ StringSyntax createFakeModulePath(string templateLinkExpression) return SyntaxHelpers.CreateArray(syntaxItems); } - private SyntaxBase? TryGetScopeProperty(JObject resource) + private SyntaxBase? TryModuleGetScopeProperty(JObject resource) { var subscriptionId = TemplateHelpers.GetProperty(resource, "subscriptionId"); var resourceGroup = TemplateHelpers.GetProperty(resource, "resourceGroup"); @@ -789,7 +789,7 @@ private SyntaxBase ParseModule(JObject resource, string typeString, string nameS var properties = new List(); properties.Add(SyntaxHelpers.CreateObjectProperty("name", ParseJToken(nameString))); - var scope = TryGetScopeProperty(resource); + var scope = TryModuleGetScopeProperty(resource); if (scope is not null) { properties.Add(SyntaxHelpers.CreateObjectProperty("scope", scope)); @@ -854,7 +854,28 @@ private SyntaxBase ParseModule(JObject resource, string typeString, string nameS SyntaxHelpers.CreateObject(properties)); } - public SyntaxBase ParseResource(JObject template, JToken value) + private SyntaxBase? TryGetResourceScopeProperty(JObject resource) + { + if (TemplateHelpers.GetProperty(resource, "scope") is not JProperty scopeProperty) + { + return null; + } + + var scopeExpression = ExpressionHelpers.ParseExpression(scopeProperty.Value.Value()); + if (TryLookupResource(scopeExpression) is string resourceName) + { + return SyntaxHelpers.CreateIdentifier(resourceName); + } + + if (TryParseStringExpression(scopeExpression) is SyntaxBase parsedSyntax) + { + return parsedSyntax; + } + + throw new ConversionFailedException($"Parsing failed for property value {scopeProperty}", scopeProperty); + } + + public SyntaxBase ParseResource(JToken value) { var resource = (value as JObject) ?? throw new ConversionFailedException($"Incorrect resource format", value); @@ -886,6 +907,7 @@ public SyntaxBase ParseResource(JObject template, JToken value) "properties", "dependsOn", "comments", + "scope", }, StringComparer.OrdinalIgnoreCase); var resourcePropsToOmit = new HashSet(new [] { @@ -894,10 +916,10 @@ public SyntaxBase ParseResource(JObject template, JToken value) "apiVersion", "dependsOn", "comments", + "scope", }, StringComparer.OrdinalIgnoreCase); TemplateHelpers.AssertUnsupportedProperty(resource, "copy", "The 'copy' property is not supported"); - TemplateHelpers.AssertUnsupportedProperty(resource, "scope", "The 'scope' property is not supported"); var topLevelProperties = new List(); foreach (var prop in resource.Properties()) @@ -921,6 +943,12 @@ public SyntaxBase ParseResource(JObject template, JToken value) topLevelProperties.Add(SyntaxHelpers.CreateObjectProperty(prop.Name, valueSyntax)); } + var scope = TryGetResourceScopeProperty(resource); + if (scope is not null) + { + topLevelProperties.Add(SyntaxHelpers.CreateObjectProperty("scope", scope)); + } + var dependsOn = ProcessDependsOn(resource); if (dependsOn != null) { @@ -1025,7 +1053,7 @@ private ProgramSyntax Parse() AddSyntaxBlock(statements, parameters.Select(ParseParam), false); AddSyntaxBlock(statements, variables.Select(ParseVariable), false); - AddSyntaxBlock(statements, flattenedResources.Select(r => ParseResource(template, r)), true); + AddSyntaxBlock(statements, flattenedResources.Select(ParseResource), true); AddSyntaxBlock(statements, outputs.Select(ParseOutput), false); return new ProgramSyntax(