Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed generating scope for looped modules and nested resources #4639

Merged
merged 4 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions src/Bicep.Core.IntegrationTests/Scenarios/ScopeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions.Execution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;

namespace Bicep.Core.IntegrationTests.Scenarios
{
[TestClass]
public class ScopeTests
{
[TestMethod]
public void UsingIndexValueInModuleScope_ShouldInlineTheResourceGroupName()
{
var result = CompilationHelper.Compile(("main.bicep", @"
targetScope = 'subscription'

// parameters
param tags object
param location string

// variables
var resourceGroups = [
'test-uat-rg'
'test-uat-blue-rg'
'test-uat-green-rg'
]

// base resource group deployment
resource rgs 'Microsoft.Resources/resourceGroups@2021-04-01' = [for rgname in resourceGroups: {
name: rgname
location: location
tags: tags
}]

module storage 'storage.bicep' = {
name: 'str'
scope: rgs[0]
params: {
environment: 'uat'
}
}
"), ("storage.bicep", @"
param environment string
output env string = environment
"));

result.Should().NotHaveAnyDiagnostics();
using (new AssertionScope())
{
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'str')].resourceGroup", "[variables('resourceGroups')[0]]");
}
}

[TestMethod]
public void UsingIndexValueInModuleScope_ShouldInlineTheManagementGroupName_1()
{
var result = CompilationHelper.Compile(("main.bicep", @"
targetScope = 'tenant'

// variables
var managementGroups = [
'mg1'
'mg2'
]

resource mgs 'Microsoft.Management/managementGroups@2020-02-01' = [for mgname in managementGroups: {
name: mgname
}]


module mod 'mod.bicep' = {
name: 'mod'
scope: mgs[0]
params: {
environment: 'uat'
}
}
"), ("mod.bicep", @"
targetScope = 'managementGroup'
param environment string
output env string = environment
"));

result.Should().NotHaveAnyDiagnostics();
using (new AssertionScope())
{
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'mod')].scope", "[format('Microsoft.Management/managementGroups/{0}', variables('managementGroups')[0])]");
}
}

[TestMethod]
public void UsingIndexValueInModuleScope_ShouldInlineTheManagementGroupName_2()
{
var result = CompilationHelper.Compile(("main.bicep", @"
targetScope = 'tenant'

// variables
var managementGroups = [
'mg1'
'mg2'
]

resource mgs 'Microsoft.Management/managementGroups@2020-02-01' existing = [for mgname in managementGroups: {
name: mgname
}]


module mod 'mod.bicep' = {
name: 'mod'
scope: mgs[0]
params: {
environment: 'uat'
}
}
"), ("mod.bicep", @"
targetScope = 'managementGroup'
param environment string
output env string = environment
"));

result.Should().NotHaveAnyDiagnostics();
using (new AssertionScope())
{
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'mod')].scope", "[format('Microsoft.Management/managementGroups/{0}', variables('managementGroups')[0])]");
}
}

[TestMethod]
public void UsingIndexValueInModuleScope_ShouldInlineTheResourceGroupAndSubscription()
{
var result = CompilationHelper.Compile(("main.bicep", @"
targetScope = 'subscription'

// variables
var resourceGroups = [
{
name: 'test-uat-rg'
sub: '00000000-0000-0000-0000-000000000000'
}
]

// base resource group deployment
resource rgs 'Microsoft.Resources/resourceGroups@2021-04-01' existing = [for rg in resourceGroups: {
name: rg.name
scope: subscription(rg.sub)
}]

module storage 'storage.bicep' = {
name: 'str'
scope: rgs[0]
params: {
environment: 'uat'
}
}
"), ("storage.bicep", @"
param environment string
output env string = environment
"));

result.Should().NotHaveAnyDiagnostics();
using (new AssertionScope())
{
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'str')].resourceGroup", "[variables('resourceGroups')[0].name]");
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'str')].subscriptionId", "[variables('resourceGroups')[0].sub]");
}
}
[TestMethod]
public void ScopeToNestedChildResource_ShouldGenerateScopeIdCorrectly()
{
var result = CompilationHelper.Compile(("main.bicep", @"
param postgreSqlServerId string
var PSQL_DATABASES = [
{
database: {
name: 'db1'
}
}
{
database: {
name: 'db2'
}
}
]

resource postgreSQL 'Microsoft.DBForPostgreSQL/servers@2017-12-01' existing = {
name: last(split(postgreSqlServerId, '/'))
resource database 'databases' = [for (item, index) in PSQL_DATABASES: {
name: item.database.name
properties: {
charset: contains(item.database, 'charset') ? item.database.charset : 'utf8'
collation: contains(item.database, 'collation') ? item.database.charset : 'English_United States.1252'
}
}]
}

resource dbLocks 'Microsoft.Authorization/locks@2016-09-01' = [for (item, index) in PSQL_DATABASES: {
name: 'dbLock'
scope: postgreSQL::database[index]
properties: {
level: 'CanNotDelete'
notes: 'Database cannot be deleted'
}
}]
"));

result.Should().NotHaveAnyDiagnostics();
using (new AssertionScope())
{
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'dbLock')].scope", "[format('Microsoft.DBforPostgreSQL/servers/{0}/databases/{1}', last(split(parameters('postgreSqlServerId'), '/')), variables('PSQL_DATABASES')[copyIndex()].database.name)]");
result.Template.Should().HaveValueAtPath("$.resources[?(@.name == 'dbLock')].dependsOn", new JArray
{
"[resourceId('Microsoft.DBforPostgreSQL/servers/databases', last(split(parameters('postgreSqlServerId'), '/')), variables('PSQL_DATABASES')[copyIndex()].database.name)]"
});
}
}
}
}
17 changes: 15 additions & 2 deletions src/Bicep.Core/Emit/ExpressionEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ public void EmitExpression(SyntaxBase syntax)
}
}

public void EmitExpression(SyntaxBase resourceNameSyntax, SyntaxBase? indexExpression, SyntaxBase newContext)
{
var converterForContext = converter.CreateConverterForIndexReplacement(resourceNameSyntax, indexExpression, newContext);

var expression = converterForContext.ConvertExpression(resourceNameSyntax);
var serialized = ExpressionSerializer.SerializeExpression(expression);

writer.WriteValue(serialized);
}

public void EmitUnqualifiedResourceId(ResourceMetadata resource, SyntaxBase? indexExpression, SyntaxBase newContext)
{
var converterForContext = converter.CreateConverterForIndexReplacement(resource.NameSyntax, indexExpression, newContext);
Expand Down Expand Up @@ -142,8 +152,11 @@ public LanguageExpression GetFullyQualifiedResourceName(ResourceMetadata resourc
return converter.GetFullyQualifiedResourceName(resource);
}

public LanguageExpression GetManagementGroupResourceId(SyntaxBase managementGroupNameProperty, bool fullyQualified)
=> converter.GenerateManagementGroupResourceId(managementGroupNameProperty, fullyQualified);
public LanguageExpression GetManagementGroupResourceId(SyntaxBase managementGroupNameProperty, SyntaxBase? indexExpression, SyntaxBase newContext, bool fullyQualified)
{
var converterForContext = converter.CreateConverterForIndexReplacement(managementGroupNameProperty, indexExpression, newContext);
return converterForContext.GenerateManagementGroupResourceId(managementGroupNameProperty, fullyQualified);
}

public void EmitLanguageExpression(SyntaxBase syntax)
{
Expand Down
Loading