diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md
index abe0ca5715..0e04a27e2d 100644
--- a/docs/CHANGELOG-v1.md
+++ b/docs/CHANGELOG-v1.md
@@ -29,6 +29,13 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
## Unreleased
+- New features:
+ - Added support for expanding from `.jsonc` parameter files by @BernieWhite.
+ [#2053](https://github.com/Azure/PSRule.Rules.Azure/issues/2053)
+ - Previously only parameter files with the `.json` extension where automatically expanded.
+ - This feature adds support so that JSON parameter files with the `.jsonc` extension are also discovered and expanded.
+ - No additional configuration is required if expansion of JSON parameter files is enabled.
+ - To enable parameter file expansion set the `AZURE_PARAMETER_FILE_EXPANSION` configuration option to `true`.
- Bug fixes:
- Fixed projection of default role authorization property `principalType` by @BernieWhite.
[#3163](https://github.com/Azure/PSRule.Rules.Azure/issues/3163)
diff --git a/docs/using-templates.md b/docs/using-templates.md
index b6edae6f99..421017c05a 100644
--- a/docs/using-templates.md
+++ b/docs/using-templates.md
@@ -7,8 +7,8 @@ author: BernieWhite
PSRule for Azure discovers and analyzes Azure resources contained within template and parameter files.
To enable this feature, you need to:
-- Enable expansion.
-- Link parameter files to templates.
+1. Enable expansion.
+2. Link parameter files to templates.
!!! Abstract
This topic covers how you can validate Azure resources within template `.json` files.
@@ -24,6 +24,15 @@ configuration:
AZURE_PARAMETER_FILE_EXPANSION: true
```
+After enabling expansion, PSRule for Azure will start to discover ARM templates and Bicep module from parameter files.
+Supported parameter files must:
+
+- The use `.parameters.json` or `.parameters.jsonc` suffix. i.e. `deploy.parameters.jsonc`.
+- The use either of the following schemas:
+ - `https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#` OR
+ - `https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#`
+- Must be linked to a template or Bicep module using one of the methods described below.
+
## Linking templates
PSRule for Azure automatically detects parameter files and uses the following logic to link templates or Bicep modules.
@@ -36,7 +45,7 @@ PSRule for Azure automatically detects parameter files and uses the following lo
For details on both options continue reading.
!!! Tip
- Linking templates also applies to Bicep modules when you are using `.json` parameter files.
+ Linking templates also applies to Bicep modules when you are using JSON parameter files.
### By metadata
@@ -123,12 +132,18 @@ When metadata links are not set, PSRule will fallback to use a naming convention
PSRule for Azure supports linking by naming convention when:
-- Parameter files end with `.parameters.json` linking to ARM templates or Bicep modules.
+- Parameter files end with `.parameters.json` or `.parameters.jsonc` linking to ARM templates or Bicep modules.
- The parameter file prefix matches the file name of the template or Bicep module.
- For example, `azuredeploy.parameters.json` links to `azuredeploy.json` or `azuredeploy.bicep`.
-- If both an ARM template and Bicep module exist, the template (`.json`) is preferred.
- For example, `azuredeploy.parameters.json` chooses `azuredeploy.json` over `azuredeploy.bicep` if both exist.
+ For example, `azuredeploy.parameters.json` links to `azuredeploy.json`, `azuredeploy.jsonc`, or `azuredeploy.bicep`.
- Both parameter file and template or Bicep module must be in the same directory.
+- The following search order is used to determine the file that will be used if multiple template or Bicep files exist:
+ 1. ARM template file with the `.json` extension.
+ 2. ARM template file with the `.jsonc` extension.
+ 3. Bicep module file with the `.bicep` extension.
+
+For example, `azuredeploy.parameters.json` links to `azuredeploy.json` over `azuredeploy.jsonc` or `azuredeploy.bicep`.
+When `azuredeploy.json` does not exist, `azuredeploy.jsonc` is chosen.
+Finally `azuredeploy.parameters.json` will link to `azuredeploy.bicep` if neither of the other files exist.
The following is not currently supported:
diff --git a/src/PSRule.Rules.Azure/Data/Template/TemplateLinkHelper.cs b/src/PSRule.Rules.Azure/Data/Template/TemplateLinkHelper.cs
index 1f383985a5..7e14256041 100644
--- a/src/PSRule.Rules.Azure/Data/Template/TemplateLinkHelper.cs
+++ b/src/PSRule.Rules.Azure/Data/Template/TemplateLinkHelper.cs
@@ -24,6 +24,7 @@ internal sealed class TemplateLinkHelper
private const string PARAMETER_FILE_SUFFIX = ".parameters";
private const string TEMPLATE_FILE_EXTENSION_JSON = ".json";
+ private const string TEMPLATE_FILE_EXTENSION_JSONC = ".jsonc";
private const string TEMPLATE_FILE_EXTENSION_BICEP = ".bicep";
private const char SLASH = '/';
@@ -180,11 +181,20 @@ private static bool TryTemplateByName(string suffix, string parameterFile, out s
var baseFilename = filename.Remove(filename.Length - suffix.Length);
var jsonTemplateFile = Path.Combine(parentPath, string.Concat(baseFilename, TEMPLATE_FILE_EXTENSION_JSON));
+ var jsoncTemplateFile = Path.Combine(parentPath, string.Concat(baseFilename, TEMPLATE_FILE_EXTENSION_JSONC));
var bicepTemplateFile = Path.Combine(parentPath, string.Concat(baseFilename, TEMPLATE_FILE_EXTENSION_BICEP));
if (File.Exists(jsonTemplateFile))
+ {
templateFile = jsonTemplateFile;
+ }
+ else if (File.Exists(jsoncTemplateFile))
+ {
+ templateFile = jsoncTemplateFile;
+ }
else if (File.Exists(bicepTemplateFile))
+ {
templateFile = bicepTemplateFile;
+ }
return templateFile != null;
}
diff --git a/src/PSRule.Rules.Azure/rules/Conventions.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Conventions.Rule.ps1
index de501198df..4600a3b619 100644
--- a/src/PSRule.Rules.Azure/rules/Conventions.Rule.ps1
+++ b/src/PSRule.Rules.Azure/rules/Conventions.Rule.ps1
@@ -85,7 +85,7 @@ Export-PSRuleConvention 'Azure.Context' -Initialize {
}
# Synopsis: Expand Azure resources from parameter files.
-Export-PSRuleConvention 'Azure.ExpandTemplate' -If { $Configuration.AZURE_PARAMETER_FILE_EXPANSION -eq $True -and $TargetObject.Extension -eq '.json' -and $Assert.HasJsonSchema($PSRule.GetContentFirstOrDefault($TargetObject), @(
+Export-PSRuleConvention 'Azure.ExpandTemplate' -If { $Configuration.AZURE_PARAMETER_FILE_EXPANSION -eq $True -and ($TargetObject.Extension -eq '.json' -or $TargetObject.Extension -eq '.jsonc') -and $Assert.HasJsonSchema($PSRule.GetContentFirstOrDefault($TargetObject), @(
"https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json`#"
"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json`#"
), $True) } -Begin {
diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.Cosmos.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.Cosmos.Tests.ps1
index 447f272a54..2fed0b2784 100644
--- a/tests/PSRule.Rules.Azure.Tests/Azure.Cosmos.Tests.ps1
+++ b/tests/PSRule.Rules.Azure.Tests/Azure.Cosmos.Tests.ps1
@@ -173,7 +173,7 @@ Describe 'Azure.Cosmos' -Tag 'Cosmos', 'CosmosDB' {
Context 'With template' {
BeforeAll {
- $templatePath = Join-Path -Path $here -ChildPath 'Resources.Cosmos.Parameters.*.json';
+ $templatePath = Join-Path -Path $here -ChildPath 'Resources.Cosmos.Parameters.*.json?';
$invokeParams = @{
Baseline = 'Azure.All'
Option = (Join-Path -Path $here -ChildPath 'test-template-options.yaml')
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 12bdc22448..9720166653 100644
--- a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
+++ b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj
@@ -176,6 +176,12 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.json b/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.json
deleted file mode 100644
index 8f7a8608e2..0000000000
--- a/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "template": "./Resources.Cosmos.Template.json"
- },
- "parameters": {
- "accountName": {
- "value": "gremlin-002"
- }
- }
-}
diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.jsonc b/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.jsonc
new file mode 100644
index 0000000000..41734b710c
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/Resources.Cosmos.Parameters.2.jsonc
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "./Resources.Cosmos.Template.json"
+ },
+ "parameters": {
+ // The account name of the CosmosDB account.
+ "accountName": {
+ "value": "gremlin-002"
+ }
+ }
+}
diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateLinkTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateLinkTests.cs
index 2fb13e715f..b40b68e220 100644
--- a/tests/PSRule.Rules.Azure.Tests/TemplateLinkTests.cs
+++ b/tests/PSRule.Rules.Azure.Tests/TemplateLinkTests.cs
@@ -23,7 +23,7 @@ public void Pipeline()
}
[Fact]
- public void GetBicepParameters()
+ public void ProcessParameterFile_WhenUsingReferencingBicep_ShouldLinkToBicep()
{
var helper = new TemplateLinkHelper(GetContext(), AppDomain.CurrentDomain.BaseDirectory, true);
@@ -52,6 +52,23 @@ public void GetBicepParameters()
Assert.EndsWith("Tests.Bicep.2.parameters.json", link.ParameterFile);
}
+ ///
+ /// Test case for https://github.com/Azure/PSRule.Rules.Azure/issues/2053.
+ ///
+ [Fact]
+ public void ProcessParameterFile_WhenUsingJSONCExtension_ShouldLinkToTemplate()
+ {
+ var helper = new TemplateLinkHelper(GetContext(), AppDomain.CurrentDomain.BaseDirectory, true);
+
+ // From naming convention
+ var link = helper.ProcessParameterFile(GetSourcePath("azuredeploy.parameters.jsonc"));
+ Assert.NotNull(link);
+ Assert.NotNull(link.TemplateFile);
+ Assert.EndsWith("azuredeploy.jsonc", link.TemplateFile);
+ Assert.NotNull(link.ParameterFile);
+ Assert.EndsWith("azuredeploy.parameters.jsonc", link.ParameterFile);
+ }
+
#region Helper methods
private static string GetSourcePath(string fileName)
diff --git a/tests/PSRule.Rules.Azure.Tests/azuredeploy.jsonc b/tests/PSRule.Rules.Azure.Tests/azuredeploy.jsonc
new file mode 100644
index 0000000000..b073aecf62
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/azuredeploy.jsonc
@@ -0,0 +1,105 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "accountName": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Cosmos DB account."
+ }
+ },
+ "location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "The location to deploy Azure resources.",
+ "strongType": "location"
+ }
+ },
+ "disableKeyBasedMetadataWriteAccess": {
+ "type": "bool",
+ "defaultValue": false,
+ "metadata": {
+ "description": "When enabled, resource keys and tokens are limit from performing resource management operations."
+ }
+ }
+ },
+ "resources": [
+ {
+ "comments": "A test Cosmos DB account.",
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2021-04-15",
+ "name": "[parameters('accountName')]",
+ "location": "[parameters('location')]",
+ "tags": {
+ "defaultExperience": "Gremlin (graph)",
+ "hidden-cosmos-mmspecial": "",
+ "CosmosAccountType": "Production"
+ },
+ "kind": "GlobalDocumentDB",
+ "identity": {
+ "type": "None"
+ },
+ "properties": {
+ "publicNetworkAccess": "Enabled",
+ "enableAutomaticFailover": false,
+ "enableMultipleWriteLocations": false,
+ "isVirtualNetworkFilterEnabled": false,
+ "virtualNetworkRules": [],
+ "disableKeyBasedMetadataWriteAccess": "[parameters('disableKeyBasedMetadataWriteAccess')]",
+ "enableFreeTier": false,
+ "enableAnalyticalStorage": false,
+ "databaseAccountOfferType": "Standard",
+ "defaultIdentity": "FirstPartyIdentity",
+ "networkAclBypass": "None",
+ "consistencyPolicy": {
+ "defaultConsistencyLevel": "Session",
+ "maxIntervalInSeconds": 5,
+ "maxStalenessPrefix": 100
+ },
+ "locations": [
+ {
+ "locationName": "region",
+ "provisioningState": "Succeeded",
+ "failoverPriority": 0,
+ "isZoneRedundant": true
+ }
+ ],
+ "cors": [],
+ "capabilities": [
+ {
+ "name": "EnableGremlin"
+ },
+ {
+ "name": "EnableServerless"
+ }
+ ],
+ "ipRules": [
+ {
+ "ipAddressOrRange": "104.42.195.92"
+ },
+ {
+ "ipAddressOrRange": "40.76.54.131"
+ },
+ {
+ "ipAddressOrRange": "52.176.6.30"
+ },
+ {
+ "ipAddressOrRange": "52.169.50.45"
+ },
+ {
+ "ipAddressOrRange": "52.187.184.26"
+ }
+ ],
+ "backupPolicy": {
+ "type": "Periodic",
+ "periodicModeProperties": {
+ "backupIntervalInMinutes": 240,
+ "backupRetentionIntervalInHours": 8
+ }
+ },
+ "networkAclBypassResourceIds": []
+ }
+ }
+ ]
+}
diff --git a/tests/PSRule.Rules.Azure.Tests/azuredeploy.parameters.jsonc b/tests/PSRule.Rules.Azure.Tests/azuredeploy.parameters.jsonc
new file mode 100644
index 0000000000..b04a668446
--- /dev/null
+++ b/tests/PSRule.Rules.Azure.Tests/azuredeploy.parameters.jsonc
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ // The account name of the CosmosDB account.
+ "accountName": {
+ "value": "gremlin-002"
+ }
+ }
+}