Skip to content

Commit

Permalink
Permit deployable scopes to be inherited from existing resource paren…
Browse files Browse the repository at this point in the history
…ts (#4394)
  • Loading branch information
anthony-c-martin authored Sep 14, 2021
1 parent de8b847 commit 4db0ebb
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 91 deletions.
75 changes: 45 additions & 30 deletions src/Bicep.Core.IntegrationTests/NestedResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ public void Nested_resource_formats_names_and_dependsOn_correctly()
[TestMethod]
public void Nested_resource_works_with_extension_resources()
{
var (template, diags, _) = CompilationHelper.Compile(@"
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' = {
name: 'res1'
Expand All @@ -677,31 +677,28 @@ public void Nested_resource_works_with_extension_resources()
output res2childid string = res2::child.id
");

using (new AssertionScope())
{
diags.ExcludingMissingTypes().Should().BeEmpty();
result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty();

// res1
template.Should().HaveValueAtPath("$.resources[2].name", "res1");
template.Should().NotHaveValueAtPath("$.resources[2].dependsOn");
// res1
result.Template.Should().HaveValueAtPath("$.resources[2].name", "res1");
result.Template.Should().NotHaveValueAtPath("$.resources[2].dependsOn");

// res1::child1
template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]");
template.Should().HaveValueAtPath("$.resources[0].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" });
// res1::child1
result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]");
result.Template.Should().HaveValueAtPath("$.resources[0].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" });

// res2
template.Should().HaveValueAtPath("$.resources[3].name", "res2");
template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" });
// res2
result.Template.Should().HaveValueAtPath("$.resources[3].name", "res2");
result.Template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" });

// res2::child2
template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res2', 'child2')]");
template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" });
// res2::child2
result.Template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res2', 'child2')]");
result.Template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" });

template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]");
template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2");
template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2");
template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]");
}
result.Template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]");
result.Template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2");
result.Template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2");
result.Template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]");
}

[TestMethod]
Expand Down Expand Up @@ -771,10 +768,32 @@ public void Nested_resource_formats_references_correctly_for_existing_resources(
}
}

[DataTestMethod]
[DataRow("resourceGroup('other')")]
[DataRow("subscription()")]
[DataRow("managementGroup('abcdef')")]
public void Nested_resource_blocks_existing_parents_at_different_scopes(string parentScope)
{
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: " + parentScope + @"
name: 'res1'
resource child 'child1' = {
name: 'child1'
}
}
");

result.Diagnostics.ExcludingMissingTypes().Should().HaveDiagnostics(new[] {
("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."),
});
}

[TestMethod]
public void Nested_resource_blocks_existing_parents_at_different_scopes()
public void Nested_resource_allows_existing_parents_at_tenant_scope()
{
var (template, diags, _) = CompilationHelper.Compile(@"
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: tenant()
name: 'res1'
Expand All @@ -785,13 +804,9 @@ public void Nested_resource_blocks_existing_parents_at_different_scopes()
}
");

using (new AssertionScope())
{
template.Should().NotHaveValue();
diags.ExcludingMissingTypes().Should().HaveDiagnostics(new[] {
("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."),
});
}
result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty();
result.Template.Should().HaveValueAtPath("$.resources[0].scope", "/");
result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]");
}

[TestMethod]
Expand Down
68 changes: 43 additions & 25 deletions src/Bicep.Core.IntegrationTests/ParentPropertyResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void Parent_property_formats_names_and_dependsOn_correctly()
[TestMethod]
public void Parent_property_works_with_extension_resources()
{
var (template, diags, _) = CompilationHelper.Compile(@"
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' = {
name: 'res1'
}
Expand All @@ -102,25 +102,24 @@ public void Parent_property_works_with_extension_resources()
output res2childid string = res2child.id
");

using (new AssertionScope())
{
template.Should().HaveValueAtPath("$.resources[0].name", "res1");
template.Should().NotHaveValueAtPath("$.resources[0].dependsOn");
result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty();

template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res1', 'child1')]");
template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" });
result.Template.Should().HaveValueAtPath("$.resources[0].name", "res1");
result.Template.Should().NotHaveValueAtPath("$.resources[0].dependsOn");

template.Should().HaveValueAtPath("$.resources[2].name", "res2");
template.Should().HaveValueAtPath("$.resources[2].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" });
result.Template.Should().HaveValueAtPath("$.resources[1].name", "[format('{0}/{1}', 'res1', 'child1')]");
result.Template.Should().HaveValueAtPath("$.resources[1].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1', 'res1')]" });

template.Should().HaveValueAtPath("$.resources[3].name", "[format('{0}/{1}', 'res2', 'child2')]");
template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" });
result.Template.Should().HaveValueAtPath("$.resources[2].name", "res2");
result.Template.Should().HaveValueAtPath("$.resources[2].dependsOn", new JArray { "[resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1')]" });

template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]");
template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2");
template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2");
template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]");
}
result.Template.Should().HaveValueAtPath("$.resources[3].name", "[format('{0}/{1}', 'res2', 'child2')]");
result.Template.Should().HaveValueAtPath("$.resources[3].dependsOn", new JArray { "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2', 'res2')]" });

result.Template.Should().HaveValueAtPath("$.outputs['res2childprop'].value", "[reference(extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')).someProp]");
result.Template.Should().HaveValueAtPath("$.outputs['res2childname'].value", "child2");
result.Template.Should().HaveValueAtPath("$.outputs['res2childtype'].value", "Microsoft.Rp2/resource2/child2");
result.Template.Should().HaveValueAtPath("$.outputs['res2childid'].value", "[extensionResourceId(resourceId('Microsoft.Rp1/resource1/child1', 'res1', 'child1'), 'Microsoft.Rp2/resource2/child2', 'res2', 'child2')]");
}

[TestMethod]
Expand Down Expand Up @@ -192,10 +191,33 @@ public void Parent_property_formats_references_correctly_for_existing_resources(
}
}

[DataTestMethod]
[DataRow("resourceGroup('other')")]
[DataRow("subscription()")]
[DataRow("managementGroup('abcdef')")]
public void Parent_property_blocks_existing_parents_at_different_scopes(string parentScope)
{
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: " + parentScope + @"
name: 'res1'
}
resource child1 'Microsoft.Rp1/resource1/child1@2020-06-01' = {
parent: res1
name: 'child1'
}
");

result.Diagnostics.ExcludingMissingTypes().Should().HaveDiagnostics(new[] {
("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."),
});
}

[TestMethod]
public void Parent_property_blocks_existing_parents_at_different_scopes()
public void Parent_property_allows_existing_parents_at_tenant_scope()
{
var (template, diags, _) = CompilationHelper.Compile(@"
var result = CompilationHelper.Compile(@"
resource res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: tenant()
name: 'res1'
Expand All @@ -207,13 +229,9 @@ public void Parent_property_blocks_existing_parents_at_different_scopes()
}
");

using (new AssertionScope())
{
template.Should().NotHaveValue();
diags.ExcludingMissingTypes().Should().HaveDiagnostics(new[] {
("BCP165", DiagnosticLevel.Error, "Cannot deploy a resource with ancestor under a different scope. Resource \"res1\" has the \"scope\" property set."),
});
}
result.Diagnostics.ExcludingMissingTypes().Should().BeEmpty();
result.Template.Should().HaveValueAtPath("$.resources[0].scope", "/");
result.Template.Should().HaveValueAtPath("$.resources[0].name", "[format('{0}/{1}', 'res1', 'child1')]");
}

[TestMethod]
Expand Down
24 changes: 24 additions & 0 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2439,6 +2439,30 @@ param defaultAdvancedFilterObject object
result.Should().NotHaveAnyDiagnostics();
}

[TestMethod]
// https://github.com/Azure/bicep/issues/2990
public void Test_Issue2990()
{
var result = CompilationHelper.Compile(@"
targetScope = 'managementGroup'
param managementGroupName string
param subscriptionId string
resource myManagementGroup 'Microsoft.Management/managementGroups@2021-04-01' existing = {
scope: tenant()
name: managementGroupName
}
resource subscriptionAssociation 'Microsoft.Management/managementGroups/subscriptions@2021-04-01' = {
parent: myManagementGroup
name: subscriptionId
}
");

result.Should().NotHaveAnyDiagnostics();
}

[TestMethod]
// https://github.com/Azure/bicep/issues/4007
public void Test_Issue4007()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {

// parent property with 'existing' resource at different scope
resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: tenant()
scope: subscription()
name: 'res1'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {
// parent property with 'existing' resource at different scope
resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
//@[17:53) [BCP081 (Warning)] Resource type "Microsoft.Rp1/resource1@2020-06-01" does not have types available. (CodeDescription: none) |'Microsoft.Rp1/resource1@2020-06-01'|
scope: tenant()
scope: subscription()
name: 'res1'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,7 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {

// parent property with 'existing' resource at different scope
resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
scope: tenant()
scope: subscription()
name: 'res1'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1673,8 +1673,8 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {

// parent property with 'existing' resource at different scope
resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
//@[9:16) Resource p1_res1. Type: Microsoft.Rp1/resource1@2020-06-01. Declaration start char: 0, length: 104
scope: tenant()
//@[9:16) Resource p1_res1. Type: Microsoft.Rp1/resource1@2020-06-01. Declaration start char: 0, length: 110
scope: subscription()
name: 'res1'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9736,28 +9736,28 @@ resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = {
// parent property with 'existing' resource at different scope
//@[62:64) NewLine |\r\n|
resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
//@[0:104) ResourceDeclarationSyntax
//@[0:110) ResourceDeclarationSyntax
//@[0:8) Identifier |resource|
//@[9:16) IdentifierSyntax
//@[9:16) Identifier |p1_res1|
//@[17:53) StringSyntax
//@[17:53) StringComplete |'Microsoft.Rp1/resource1@2020-06-01'|
//@[54:62) Identifier |existing|
//@[63:64) Assignment |=|
//@[65:104) ObjectSyntax
//@[65:110) ObjectSyntax
//@[65:66) LeftBrace |{|
//@[66:68) NewLine |\r\n|
scope: tenant()
//@[2:17) ObjectPropertySyntax
scope: subscription()
//@[2:23) ObjectPropertySyntax
//@[2:7) IdentifierSyntax
//@[2:7) Identifier |scope|
//@[7:8) Colon |:|
//@[9:17) FunctionCallSyntax
//@[9:15) IdentifierSyntax
//@[9:15) Identifier |tenant|
//@[15:16) LeftParen |(|
//@[16:17) RightParen |)|
//@[17:19) NewLine |\r\n|
//@[9:23) FunctionCallSyntax
//@[9:21) IdentifierSyntax
//@[9:21) Identifier |subscription|
//@[21:22) LeftParen |(|
//@[22:23) RightParen |)|
//@[23:25) NewLine |\r\n|
name: 'res1'
//@[2:14) ObjectPropertySyntax
//@[2:6) IdentifierSyntax
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6292,13 +6292,13 @@ resource p1_res1 'Microsoft.Rp1/resource1@2020-06-01' existing = {
//@[63:64) Assignment |=|
//@[65:66) LeftBrace |{|
//@[66:68) NewLine |\r\n|
scope: tenant()
scope: subscription()
//@[2:7) Identifier |scope|
//@[7:8) Colon |:|
//@[9:15) Identifier |tenant|
//@[15:16) LeftParen |(|
//@[16:17) RightParen |)|
//@[17:19) NewLine |\r\n|
//@[9:21) Identifier |subscription|
//@[21:22) LeftParen |(|
//@[22:23) RightParen |)|
//@[23:25) NewLine |\r\n|
name: 'res1'
//@[2:6) Identifier |name|
//@[6:7) Colon |:|
Expand Down
Loading

0 comments on commit 4db0ebb

Please sign in to comment.