diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 01177f3952c..3e877237d5d 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -28,6 +28,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since v1.31.1: + +- Bug fixes: + - Fixed nullable parameters with JValue null by @BernieWhite. + [#2535](https://github.com/Azure/PSRule.Rules.Azure/issues/2535) + ## v1.31.1 What's changed since v1.31.0: diff --git a/src/PSRule.Rules.Azure/Data/Template/ExpressionHelpers.cs b/src/PSRule.Rules.Azure/Data/Template/ExpressionHelpers.cs index 259f4d78c5e..04b001d2605 100644 --- a/src/PSRule.Rules.Azure/Data/Template/ExpressionHelpers.cs +++ b/src/PSRule.Rules.Azure/Data/Template/ExpressionHelpers.cs @@ -195,7 +195,7 @@ internal static bool TryIndex(object o, object index, out object value) internal static bool TryPropertyOrField(object o, string propertyName, out object value) { value = null; - if (o == null) return false; + if (IsNull(o)) return false; var resultType = o.GetType(); @@ -214,7 +214,7 @@ internal static bool TryPropertyOrField(object o, string propertyName, out objec return true; } - if (o is JToken jToken) + if (o is JToken jToken && o is not JValue) { var propertyToken = jToken[propertyName]; if (propertyToken == null) diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs index 66daffa889f..10200110273 100644 --- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs @@ -906,12 +906,16 @@ public void SymbolicDependsOn() public void NullableParameters() { var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.27.json"), null, out _); - Assert.Equal(3, resources.Length); + Assert.Equal(4, resources.Length); var actual = resources[2]; Assert.Equal("Microsoft.Storage/storageAccounts", actual["type"].Value()); Assert.Equal("TLS1_2", actual["properties"]["minimumTlsVersion"].Value()); Assert.Empty(actual["resources"][0]["properties"]["cors"]["corsRules"].Value()); + + actual = resources[3]; + Assert.Equal("Microsoft.KeyVault/vaults", actual["type"].Value()); + Assert.Empty(actual["properties"]["accessPolicies"].Value()); } [Fact] diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep index c804f537959..5fa6541f3f9 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.child.bicep @@ -12,6 +12,19 @@ type corsRule = { maxAgeInSeconds: int }[]? +param accessPolicies array? + +@secure() +param secrets object? + +var formattedAccessPolicies = [for accessPolicy in (accessPolicies ?? []): { + objectId: accessPolicy.objectId + tenantId: contains(accessPolicy, 'tenantId') ? accessPolicy.tenantId : tenant().tenantId + permissions: {} +}] + +var secretList = secrets.?secureList ?? [] + resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = { name: 'test' #disable-next-line no-loc-expr-outside-params @@ -33,3 +46,24 @@ resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = { } } } + +resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = { + name: 'keyVault' + #disable-next-line no-loc-expr-outside-params + location: resourceGroup().location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + accessPolicies: formattedAccessPolicies + } +} + +resource kvSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = [for item in secretList: { + name: item.name + properties: { + value: item.value + } +}] diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json index 8b2df10a7d1..59ffb248719 100644 --- a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.27.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.23.1.45101", - "templateHash": "10495223775905612115" + "templateHash": "15341554075500882140" } }, "resources": [ @@ -27,7 +27,7 @@ "_generator": { "name": "bicep", "version": "0.23.1.45101", - "templateHash": "8612041935519591936" + "templateHash": "1045026030740697206" } }, "definitions": { @@ -75,8 +75,30 @@ }, "corsRules": { "$ref": "#/definitions/corsRule" + }, + "accessPolicies": { + "type": "array", + "nullable": true + }, + "secrets": { + "type": "secureObject", + "nullable": true } }, + "variables": { + "copy": [ + { + "name": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "tenantId": "[if(contains(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].tenantId, tenant().tenantId)]", + "permissions": {} + } + } + ], + "secretList": "[coalesce(tryGet(parameters('secrets'), 'secureList'), createArray())]" + }, "resources": { "storage::blob": { "type": "Microsoft.Storage/storageAccounts/blobServices", @@ -103,6 +125,32 @@ "properties": { "minimumTlsVersion": "[coalesce(parameters('minTLSVersion'), 'TLS1_2')]" } + }, + "kv": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-07-01", + "name": "keyVault", + "location": "[resourceGroup().location]", + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[tenant().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]" + } + }, + "kvSecret": { + "copy": { + "name": "kvSecret", + "count": "[length(variables('secretList'))]" + }, + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-07-01", + "name": "[variables('secretList')[copyIndex()].name]", + "properties": { + "value": "[variables('secretList')[copyIndex()].value]" + } } } }