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

Add validation for parent property #7254

Merged
merged 8 commits into from
Jun 21, 2022
Merged
163 changes: 161 additions & 2 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,7 @@ param zonesEnabled bool
("BCP036", DiagnosticLevel.Error, "The property \"scope\" expected a value of type \"resource | tenant\" but the provided value is of type \"null\"."),
("BCP036", DiagnosticLevel.Error, "The property \"name\" expected a value of type \"string\" but the provided value is of type \"null\"."),
("BCP036", DiagnosticLevel.Error, "The property \"parent\" expected a value of type \"Microsoft.Network/dnsZones\" but the provided value is of type \"null\"."),
("BCP240", DiagnosticLevel.Error, "The \"parent\" property only permits direct references to resources. Expressions are not supported."),
});
}

Expand Down Expand Up @@ -2417,7 +2418,9 @@ public void Test_Issue4212()
result.Should().HaveDiagnostics(new[]
{
("BCP036", DiagnosticLevel.Error, "The property \"parent\" expected a value of type \"Microsoft.Network/virtualNetworks\" but the provided value is of type \"module\"."),
("BCP240", DiagnosticLevel.Error, "The \"parent\" property only permits direct references to resources. Expressions are not supported."),
("BCP036", DiagnosticLevel.Error, "The property \"parent\" expected a value of type \"Microsoft.Network/virtualNetworks\" but the provided value is of type \"tenant\"."),
("BCP240", DiagnosticLevel.Error, "The \"parent\" property only permits direct references to resources. Expressions are not supported."),
});
}

Expand Down Expand Up @@ -3427,18 +3430,174 @@ public void Test_Issue_7241_1()
public void Test_Issue_7241_2(string copy)
{
var result = CompilationHelper.Compile(@"
var "+copy+@" = {}
var " + copy + @" = {}
");


using (new AssertionScope())
{
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new []
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[]
{
("BCP239", DiagnosticLevel.Error, "Identifier \"copy\" is a reserved Bicep symbol name and cannot be used in this context.")
});
result.Template.Should().BeNull();
}
}

/// <summary>
/// https://github.com/Azure/bicep/issues/7154
/// </summary>
[TestMethod]
public void Test_Issue_7154_Ternary_Syntax_Produces_Error()
{
var result = CompilationHelper.Compile(@"
var deployServerlessCosmosDb = true

resource cosmosDbServer 'Microsoft.DocumentDB/databaseAccounts@2021-07-01-preview' = {
kind: 'GlobalDocumentDB'
name: 'cosmosdbname'
location: resourceGroup().location
properties: {
createMode: 'Default'
locations: [
{
locationName: resourceGroup().location
failoverPriority: 0
}
]
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
maxIntervalInSeconds: 5
maxStalenessPrefix: 100
}
diagnosticLogSettings: {
enableFullTextQuery: 'None'
}
}
}

resource PassDb 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-07-01-preview' = {
parent: cosmosDbServer
name: 'PassDb'
properties: {
resource: {
id: 'PassDb'
}
}
}

resource QPDB 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-07-01-preview' = if(!deployServerlessCosmosDb) {
parent: cosmosDbServer
name: 'QPDB'
properties: {
resource: {
id: 'QPDB'
}
}
}

resource container_ActorColdStorage 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-07-01-preview' = {
parent: deployServerlessCosmosDb? PassDb: QPDB
name: 'ActorColdStorage'
properties: {
resource: {
id: 'ActorColdStorage'
partitionKey: {
paths: [
'/Type'
]
kind: 'Hash'
}
conflictResolutionPolicy: {
mode: 'LastWriterWins'
conflictResolutionPath: '/_ts'
}
}
}
}
");
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[] {
("BCP240", DiagnosticLevel.Error, "The \"parent\" property only permits direct references to resources. Expressions are not supported.")
});
}

/// <summary>
/// https://github.com/Azure/bicep/issues/7154
/// </summary>
[TestMethod]
public void Test_Issue_7154_2_Ternary_Syntax_With_Parentheses_Produces_Error()
{
var result = CompilationHelper.Compile(@"
var deployServerlessCosmosDb = true

resource cosmosDbServer 'Microsoft.DocumentDB/databaseAccounts@2021-07-01-preview' = {
kind: 'GlobalDocumentDB'
name: 'cosmosdbname'
location: resourceGroup().location
properties: {
createMode: 'Default'
locations: [
{
locationName: resourceGroup().location
failoverPriority: 0
}
]
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
maxIntervalInSeconds: 5
maxStalenessPrefix: 100
}
diagnosticLogSettings: {
enableFullTextQuery: 'None'
}
}
}

resource PassDb 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-07-01-preview' = {
parent: cosmosDbServer
name: 'PassDb'
properties: {
resource: {
id: 'PassDb'
}
}
}

resource QPDB 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-07-01-preview' = if(!deployServerlessCosmosDb) {
parent: cosmosDbServer
name: 'QPDB'
properties: {
resource: {
id: 'QPDB'
}
}
}

resource container_ActorColdStorage 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-07-01-preview' = {
parent: (deployServerlessCosmosDb)? PassDb: QPDB
name: 'ActorColdStorage'
properties: {
resource: {
id: 'ActorColdStorage'
partitionKey: {
paths: [
'/Type'
]
kind: 'Hash'
}
conflictResolutionPolicy: {
mode: 'LastWriterWins'
conflictResolutionPath: '/_ts'
}
}
}
}
");
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[] {
("BCP240", DiagnosticLevel.Error, "The \"parent\" property only permits direct references to resources. Expressions are not supported.")
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1989,12 +1989,14 @@ resource anyTypeInDependsOn 'Microsoft.Network/dnsZones@2018-05-01' = {
resource anyTypeInParent 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = {
//@[009:024) [BCP035 (Error)] The specified "resource" declaration is missing the following required properties: "name". (CodeDescription: none) |anyTypeInParent|
parent: any(true)
//@[010:019) [BCP240 (Error)] The "parent" property only permits direct references to resources. Expressions are not supported. (CodeDescription: none) |any(true)|
//@[010:019) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any(true)|
}

resource anyTypeInParentLoop 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = [for thing in []: {
//@[009:028) [BCP035 (Error)] The specified "resource" declaration is missing the following required properties: "name". (CodeDescription: none) |anyTypeInParentLoop|
parent: any(true)
//@[010:019) [BCP240 (Error)] The "parent" property only permits direct references to resources. Expressions are not supported. (CodeDescription: none) |any(true)|
//@[010:019) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any(true)|
}]

Expand All @@ -2013,6 +2015,7 @@ resource anyTypeInScopeConditional 'Microsoft.Authorization/locks@2016-09-01' =
resource anyTypeInExistingScope 'Microsoft.Network/dnsZones/AAAA@2018-05-01' existing = {
//@[009:031) [BCP035 (Error)] The specified "resource" declaration is missing the following required properties: "name". (CodeDescription: none) |anyTypeInExistingScope|
parent: any('')
//@[010:017) [BCP240 (Error)] The "parent" property only permits direct references to resources. Expressions are not supported. (CodeDescription: none) |any('')|
//@[010:017) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any('')|
scope: any(false)
//@[009:019) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any(false)|
Expand All @@ -2021,6 +2024,7 @@ resource anyTypeInExistingScope 'Microsoft.Network/dnsZones/AAAA@2018-05-01' exi
resource anyTypeInExistingScopeLoop 'Microsoft.Network/dnsZones/AAAA@2018-05-01' existing = [for thing in []: {
//@[009:035) [BCP035 (Error)] The specified "resource" declaration is missing the following required properties: "name". (CodeDescription: none) |anyTypeInExistingScopeLoop|
parent: any('')
//@[010:017) [BCP240 (Error)] The "parent" property only permits direct references to resources. Expressions are not supported. (CodeDescription: none) |any('')|
//@[010:017) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any('')|
scope: any(false)
//@[009:019) [BCP176 (Error)] Values of the "any" type are not allowed here. (CodeDescription: none) |any(false)|
Expand Down
5 changes: 5 additions & 0 deletions src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,11 @@ public ErrorDiagnostic UnknownModuleReferenceScheme(string badScheme, ImmutableA
TextSpan,
"BCP239",
$"Identifier \"{name}\" is a reserved Bicep symbol name and cannot be used in this context.");

public ErrorDiagnostic InvalidValueForParentProperty() => new(
TextSpan,
"BCP240",
"The \"parent\" property only permits direct references to resources. Expressions are not supported.");
}

public static DiagnosticBuilderInternal ForPosition(TextSpan span)
Expand Down
19 changes: 19 additions & 0 deletions src/Bicep.Core/Emit/EmitLimitationCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static EmitLimitationInfo Calculate(SemanticModel model)
DetectUnexpectedModuleLoopInvariantProperties(model, diagnosticWriter);
DetectUnsupportedModuleParameterAssignments(model, diagnosticWriter);
DetectCopyVariableName(model, diagnosticWriter);
DetectInvalidValueForParentProperty(model, diagnosticWriter);

return new(diagnosticWriter.GetDiagnostics(), moduleScopeData, resourceScopeData);
}
Expand Down Expand Up @@ -328,6 +329,24 @@ private static void DetectCopyVariableName(SemanticModel semanticModel, IDiagnos
}
}

public static void DetectInvalidValueForParentProperty(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
foreach (var resourceDeclarationSymbol in semanticModel.Root.ResourceDeclarations)
{
if (resourceDeclarationSymbol.TryGetBodyPropertyValue(LanguageConstants.ResourceParentPropertyName) is { } referenceParentSyntax)
{
var (baseSyntax, _) = SyntaxHelper.UnwrapArrayAccessSyntax(referenceParentSyntax);

if (semanticModel.ResourceMetadata.TryLookup(baseSyntax) is not { } && !semanticModel.GetTypeInfo(baseSyntax).IsError())
{
// we throw an error diagnostic when the parent property contains a value that cannot be computed or does not directly reference another resource.
// this includes ternary operator expressions, which Bicep does not support
diagnosticWriter.Write(referenceParentSyntax, x => x.InvalidValueForParentProperty());
}
}
}
}

private static bool IsInvariant(SemanticModel semanticModel, LocalVariableSyntax itemVariable, LocalVariableSyntax? indexVariable, SyntaxBase expression)
{
var referencedLocals = LocalSymbolDependencyVisitor.GetLocalSymbolDependencies(semanticModel, expression);
Expand Down