diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md
index dcbbe805dc1..fe109161aa2 100644
--- a/docs/CHANGELOG-v1.md
+++ b/docs/CHANGELOG-v1.md
@@ -28,7 +28,7 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
## Unreleased
-What's changed since v1.30.2:
+What's changed since v1.30.3:
- Engineering:
- Bump development tools to .NET 7.0 SDK by @BernieWhite.
@@ -36,6 +36,14 @@ What's changed since v1.30.2:
- Bump BenchmarkDotNet to v0.13.9.
[#2469](https://github.com/Azure/PSRule.Rules.Azure/pull/2469)
+## v1.30.3
+
+What's changed since v1.30.2:
+
+- Bug fixes:
+ - Fixed nullable parameters for built-in types by @BernieWhite.
+ [#2488](https://github.com/Azure/PSRule.Rules.Azure/issues/2488)
+
## v1.30.2
What's changed since v1.30.1:
diff --git a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs
index 8c49831c8b4..78dad147b87 100644
--- a/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs
+++ b/src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs
@@ -11,6 +11,7 @@
using PSRule.Rules.Azure.Configuration;
using PSRule.Rules.Azure.Pipeline;
using PSRule.Rules.Azure.Resources;
+using YamlDotNet.Core.Tokens;
namespace PSRule.Rules.Azure.Data.Template
{
@@ -66,6 +67,7 @@ internal abstract class TemplateVisitor : ResourceManagerVisitor
private const string PROPERTY_DEFINITIONS = "definitions";
private const string PROPERTY_REF = "$ref";
private const string PROPERTY_ROOTDEPLOYMENT = "rootDeployment";
+ private const string PROPERTY_NULLABLE = "nullable";
internal sealed class TemplateContext : ITemplateContext
{
@@ -960,7 +962,8 @@ private static bool TryParameter(TemplateContext context, string parameterName,
return parameter == null ||
TryParameterAssignment(context, parameterName, parameter) ||
TryParameterDefaultValue(context, parameterName, parameter) ||
- TryParameterDefault(context, parameterName, parameter);
+ TryParameterDefault(context, parameterName, parameter) ||
+ TryParameterNullable(context, parameterName, parameter);
}
private static bool TryParameterAssignment(TemplateContext context, string parameterName, JObject parameter)
@@ -1003,6 +1006,21 @@ private static bool TryParameterDefault(TemplateContext context, string paramete
return true;
}
+ ///
+ /// Handle cases when the parameter has been marked as nullable.
+ ///
+ private static bool TryParameterNullable(TemplateContext context, string parameterName, JObject parameter)
+ {
+ if (!parameter.TryBoolProperty(PROPERTY_NULLABLE, out var nullable) || !nullable.HasValue)
+ return false;
+
+ if (!TryParameterType(context, parameter, out var type))
+ throw ThrowTemplateParameterException(parameterName);
+
+ AddParameterFromType(context, parameterName, type.Value, JToken.Parse("null"));
+ return true;
+ }
+
private static bool TryParameterType(ITemplateContext context, JObject parameter, out ParameterType? value)
{
value = null;
diff --git a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
index 12ee951399f..dde46fa9db5 100644
--- a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
+++ b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
@@ -191,6 +191,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
index 803098bf3b3..c31aee47b21 100644
--- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
+++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
@@ -876,6 +876,17 @@ public void ArrayContains()
Assert.Equal("Standard", resources[10]["properties"]["pricingTier"].Value());
}
+ [Fact]
+ public void NullableParameters()
+ {
+ var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.27.json"), null, out _);
+ Assert.Equal(3, resources.Length);
+
+ var actual = resources[2];
+ Assert.Equal("Microsoft.Storage/storageAccounts", actual["type"].Value());
+ Assert.Equal("TLS1_2", actual["properties"]["minimumTlsVersion"].Value());
+ }
+
#region Helper methods
private static string GetSourcePath(string fileName)
diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.bicep
new file mode 100644
index 00000000000..2482bdb849a
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.bicep
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+module child 'Tests.Bicep.27.child.bicep' = {
+ name: 'child'
+ params: {}
+}
diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep
new file mode 100644
index 00000000000..234dab08233
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+param minTLSVersion string?
+// param corsRules corsRule
+
+// type corsRule = {
+// allowedHeaders: string[]
+// allowedMethods: string[]
+// allowedOrigins: string[]
+// exposedHeaders: string[]
+// maxAgeInSeconds: int
+// }[]?
+
+resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
+ name: 'test'
+ #disable-next-line no-loc-expr-outside-params
+ location: resourceGroup().location
+ sku: {
+ name: 'Standard_LRS'
+ }
+ kind: 'StorageV2'
+ properties: {
+ minimumTlsVersion: minTLSVersion ?? 'TLS1_2'
+ }
+
+ // resource blob 'blobServices' = {
+ // name: 'default'
+ // properties: {
+ // cors: {
+ // corsRules: corsRules ?? []
+ // }
+ // }
+ // }
+}
diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json
new file mode 100644
index 00000000000..7950425182b
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.22.6.54827",
+ "templateHash": "3111791179159915120"
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2022-09-01",
+ "name": "child",
+ "properties": {
+ "expressionEvaluationOptions": {
+ "scope": "inner"
+ },
+ "mode": "Incremental",
+ "parameters": {},
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "languageVersion": "2.0",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_generator": {
+ "name": "bicep",
+ "version": "0.22.6.54827",
+ "templateHash": "965836287794218595"
+ }
+ },
+ "parameters": {
+ "minTLSVersion": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "resources": {
+ "storage": {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2023-01-01",
+ "name": "test",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "Standard_LRS"
+ },
+ "kind": "StorageV2",
+ "properties": {
+ "minimumTlsVersion": "[coalesce(parameters('minTLSVersion'), 'TLS1_2')]"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file