Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ What's changed since pre-release v1.48.0-B0088:
- General improvements:
- Updated provider data by @BernieWhite.
[#3790](https://github.com/Azure/PSRule.Rules.Azure/issues/3790)
- Added support for Bicep `resourceType` parameter metadata.
[#1474](https://github.com/Azure/PSRule.Rules.Azure/issues/1474)

## v1.48.0-B0088 (pre-release)

Expand Down
5 changes: 4 additions & 1 deletion docs/en/rules/Azure.Template.ParameterStrongType.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ Set the parameter value to a value that matches the specified strong type.
## DESCRIPTION

Template string parameters can optionally specify a strong type.
Bicep can also emit resource type metadata for string parameters.
When parameter files are expanded, if the parameter value does not match the type this rule fails.
Support is provided by PSRule for Azure for the following types:

- Resource type - Specify a resource type.
For example `Microsoft.OperationalInsights/workspaces`.
If a resource type is specified the parameter value must be a resource id of that type.
This can be set by using `metadata.strongType` or Bicep `metadata.resourceType`.
For `metadata.resourceType`, any API version after `@` is ignored.
- Location - Specify `location` as the strong type.
If `location` is specified, the parameter value must be a valid Azure location.

## RECOMMENDATION

Consider updating the parameter value to a value that matches the specifed strong type.
Consider updating the parameter value to a value that matches the specified strong type.

## LINKS

Expand Down
17 changes: 15 additions & 2 deletions src/PSRule.Rules.Azure/Data/Template/TemplateValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal sealed class TemplateValidator
{
private const string PROPERTY_METADATA = "metadata";
private const string PROPERTY_STRONG_TYPE = "strongType";
private const string PROPERTY_RESOURCE_TYPE = "resourceType";
private const string STRONG_TYPE_LOCATION = "location";

private const string ISSUE_PARAMETER_STRONG_TYPE = "PSRule.Rules.Azure.Template.ParameterStrongType";
Expand All @@ -26,6 +27,7 @@ internal sealed class TemplateValidator
private const string RESOURCE_TYPE_RESOURCE_GROUPS = "resourceGroups";

private const string SLASH = "/";
private const char API_VERSION_SEPARATOR = '@';

private ISet<string> _Locations;

Expand Down Expand Up @@ -108,14 +110,25 @@ private void ParameterStrongType(IValidationContext context, string parameterNam
private static bool TryStrongType(JObject parameter, out string strongType)
{
strongType = null;
if (parameter.TryGetProperty(PROPERTY_METADATA, out JObject metadata) &&
metadata.TryGetProperty(PROPERTY_STRONG_TYPE, out JValue st) &&
if (!parameter.TryGetProperty(PROPERTY_METADATA, out JObject metadata))
return false;

if (metadata.TryGetProperty(PROPERTY_STRONG_TYPE, out JValue st) &&
st.Value<string>() is string value)
strongType = value;
else if (metadata.TryGetProperty(PROPERTY_RESOURCE_TYPE, out JValue rt) &&
rt.Value<string>() is string resourceType)
strongType = ResourceTypeWithoutApiVersion(resourceType);

return strongType != null;
}

private static string ResourceTypeWithoutApiVersion(string resourceType)
{
var index = resourceType.IndexOf(API_VERSION_SEPARATOR);
return index < 0 ? resourceType : resourceType.Substring(0, index);
}

private void IsValidLocation(IValidationContext context, JObject parameter, string parameterName, object value)
{
if (!ExpressionHelpers.TryString(value, out var location))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@
<None Update="Template.Parsing.9.Parameters.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Template.ResourceType.1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Template.StrongType.1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
32 changes: 32 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Template.ResourceType.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountId": {
"type": "string",
"defaultValue": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Storage/storageAccounts/store1",
"metadata": {
"resourceType": "Microsoft.Storage/storageAccounts@2021-04-01"
}
},
"vnetId": {
"type": "string",
"defaultValue": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Network/virtualNetworks/vnet1",
"metadata": {
"resourceType": "Microsoft.Storage/storageAccounts@2021-04-01"
}
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "sttest001",
"location": "eastus",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}
]
}
14 changes: 14 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,20 @@ public void StrongTypeParameter()
Assert.False(actual3["_PSRule"].Value<JObject>().ContainsKey("issue"));
}

[Fact]
public void ResourceTypeParameter()
{
var resources = ProcessTemplate(GetSourcePath("Template.ResourceType.1.json"), null);
Assert.NotNull(resources);
Assert.Equal(2, resources.Length);

var actual = resources[0];
Assert.Equal("Microsoft.Resources/deployments", actual["type"].Value<string>());
Assert.Single(actual["_PSRule"]["issue"].Value<JArray>());
Assert.Equal("PSRule.Rules.Azure.Template.ParameterStrongType", actual["_PSRule"]["issue"][0]["type"].Value<string>());
Assert.Equal("vnetId", actual["_PSRule"]["issue"][0]["name"].Value<string>());
}

[Fact]
public void StrongTypeNestedParameter()
{
Expand Down
Loading