Skip to content

Commit 3a2e398

Browse files
authored
Fixed support for references function Azure#2922 (Azure#2958)
1 parent a723b9b commit 3a2e398

16 files changed

+259
-18
lines changed

bicepconfig.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"experimentalFeaturesEnabled": {
3-
"optionalModuleNames": true,
4-
"userDefinedFunctions": true
3+
"optionalModuleNames": true
54
}
65
}

docs/CHANGELOG-v1.md

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ What's changed since pre-release v1.38.0-B0034:
3434
- Engineering:
3535
- Quality updates to rule documentation by @BernieWhite.
3636
[#2570](https://github.com/Azure/PSRule.Rules.Azure/issues/2570)
37+
- Bug fixes:
38+
- Fixed support for `references` function by @BernieWhite.
39+
[#2922](https://github.com/Azure/PSRule.Rules.Azure/issues/2922)
3740

3841
## v1.38.0-B0034 (pre-release)
3942

src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs

+6
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ public bool TryGetResource(string resourceId, out IResourceValue resource)
220220
return false;
221221
}
222222

223+
public bool TryGetResourceCollection(string symbolicName, out IResourceValue[] resources)
224+
{
225+
resources = null;
226+
return false;
227+
}
228+
223229
internal bool TryParameterAssignment(string parameterName, out JToken value)
224230
{
225231
return _ParameterAssignments.TryGetValue(parameterName, out value);
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System;
45
using System.Collections.Generic;
56

67
namespace PSRule.Rules.Azure.Data.Template
78
{
89
internal sealed class ArrayDeploymentSymbol : DeploymentSymbol, IDeploymentSymbol
910
{
10-
private List<ResourceValue> _Resources;
11+
private List<string> _Ids;
1112

1213
public ArrayDeploymentSymbol(string name)
1314
: base(name) { }
@@ -16,13 +17,18 @@ public ArrayDeploymentSymbol(string name)
1617

1718
public void Configure(ResourceValue resource)
1819
{
19-
_Resources ??= new List<ResourceValue>();
20-
_Resources.Add(resource);
20+
_Ids ??= new List<string>();
21+
_Ids.Add(resource.Id);
2122
}
2223

2324
public string GetId(int index)
2425
{
25-
return _Resources[index].Id;
26+
return _Ids[index];
27+
}
28+
29+
public string[] GetIds()
30+
{
31+
return _Ids?.ToArray() ?? Array.Empty<string>();
2632
}
2733
}
2834
}

src/PSRule.Rules.Azure/Data/Template/Functions.cs

+37
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ internal static class Functions
113113
new FunctionDescriptor("pickZones", PickZones),
114114
new FunctionDescriptor("providers", Providers),
115115
new FunctionDescriptor("reference", Reference),
116+
new FunctionDescriptor("references", References),
116117
new FunctionDescriptor("resourceId", ResourceId),
117118
new FunctionDescriptor("subscriptionResourceId", SubscriptionResourceId),
118119
new FunctionDescriptor("tenantResourceId", TenantResourceId),
@@ -1013,6 +1014,37 @@ internal static object Reference(ITemplateContext context, object[] args)
10131014
: full ? new Mock.MockResource(resourceId) : new Mock.MockResource(resourceId)["properties"];
10141015
}
10151016

1017+
/// <summary>
1018+
/// references(symbolic name of a resource collection, ['Full', 'Properties'])
1019+
/// </summary>
1020+
/// <remarks>
1021+
/// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/templates/template-functions-resource#references"/>.
1022+
/// </remarks>
1023+
internal static object References(ITemplateContext context, object[] args)
1024+
{
1025+
var argCount = CountArgs(args);
1026+
if (argCount is < 1 or > 2)
1027+
throw ArgumentsOutOfRange(nameof(References), args);
1028+
1029+
string fullValue = null;
1030+
var full = argCount == 2 && ExpressionHelpers.TryString(args[1], out fullValue) && string.Equals(fullValue, PROPERTY_FULL, StringComparison.OrdinalIgnoreCase);
1031+
if (argCount == 2 && !full && !string.Equals(fullValue, PROPERTY_PROPERTIES, StringComparison.OrdinalIgnoreCase))
1032+
throw ArgumentFormatInvalid(nameof(References));
1033+
1034+
// Get symbolic name
1035+
if (!ExpressionHelpers.TryString(args[0], out var symbolicName))
1036+
throw ArgumentFormatInvalid(nameof(References));
1037+
1038+
if (!context.TryGetResourceCollection(symbolicName, out var resources))
1039+
throw ArgumentInvalidResourceCollection(nameof(References), symbolicName);
1040+
1041+
var result = new object[resources.Length];
1042+
for (var i = 0; i < resources.Length; i++)
1043+
result[i] = GetReferenceResult(resources[i], full);
1044+
1045+
return result;
1046+
}
1047+
10161048
private static object GetReferenceResult(IResourceValue resource, bool full)
10171049
{
10181050
if (resource is DeploymentValue deployment)
@@ -2481,6 +2513,11 @@ private static ExpressionArgumentException ArgumentNullNotExpected(string expres
24812513
);
24822514
}
24832515

2516+
private static Exception ArgumentInvalidResourceCollection(string v, string symbolicName)
2517+
{
2518+
throw new NotImplementedException();
2519+
}
2520+
24842521
#endregion Exceptions
24852522
}
24862523
}

src/PSRule.Rules.Azure/Data/Template/ITemplateContext.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,27 @@ internal interface ITemplateContext : IValidationContext
5757

5858
bool TryDefinition(string type, out ITypeDefinition definition);
5959

60-
bool TryGetResource(string resourceId, out IResourceValue resource);
60+
/// <summary>
61+
/// Try to get a resource from the current context.
62+
/// </summary>
63+
/// <param name="nameOrResourceId">The symbolic name or resource identifier.</param>
64+
/// <param name="resource">A resource.</param>
65+
/// <returns>Returns <c>true</c> when the resource exists.</returns>
66+
bool TryGetResource(string nameOrResourceId, out IResourceValue resource);
6167

68+
/// <summary>
69+
/// Try to get a resource collection from the current context.
70+
/// </summary>
71+
/// <param name="symbolicName">The symbolic name for the collection.</param>
72+
/// <param name="resources">A collection of resources.</param>
73+
/// <returns>Returns <c>true</c> when the symbolic name exists.</returns>
74+
bool TryGetResourceCollection(string symbolicName, out IResourceValue[] resources);
75+
76+
/// <summary>
77+
/// Write a debug message for the current execution context.
78+
/// </summary>
79+
/// <param name="message">The format message.</param>
80+
/// <param name="args">Additional arguments for the format message.</param>
6281
void WriteDebug(string message, params object[] args);
6382

6483
/// <summary>

src/PSRule.Rules.Azure/Data/Template/NestedTemplateContext.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,19 @@ public virtual bool TryVariable(string variableName, out object value)
5757
return _Inner.TryVariable(variableName, out value);
5858
}
5959

60-
public bool TryGetResource(string resourceId, out IResourceValue resource)
60+
/// <inheritdoc/>
61+
public bool TryGetResource(string nameOrResourceId, out IResourceValue resource)
62+
{
63+
return _Inner.TryGetResource(nameOrResourceId, out resource);
64+
}
65+
66+
/// <inheritdoc/>
67+
public bool TryGetResourceCollection(string symbolicName, out IResourceValue[] resources)
6168
{
62-
return _Inner.TryGetResource(resourceId, out resource);
69+
return _Inner.TryGetResourceCollection(symbolicName, out resources);
6370
}
6471

72+
/// <inheritdoc/>
6573
public void WriteDebug(string message, params object[] args)
6674
{
6775
_Inner.WriteDebug(message, args);

src/PSRule.Rules.Azure/Data/Template/ObjectDeploymentSymbol.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
namespace PSRule.Rules.Azure.Data.Template
55
{
66
internal sealed class ObjectDeploymentSymbol : DeploymentSymbol, IDeploymentSymbol
77
{
8-
private ResourceValue _Resource;
8+
private string _ResourceId;
99

1010
public ObjectDeploymentSymbol(string name, ResourceValue resource)
1111
: base(name)
@@ -18,12 +18,12 @@ public ObjectDeploymentSymbol(string name, ResourceValue resource)
1818

1919
public void Configure(ResourceValue resource)
2020
{
21-
_Resource = resource;
21+
_ResourceId = resource.Id;
2222
}
2323

2424
public string GetId(int index)
2525
{
26-
return _Resource.Id;
26+
return _ResourceId;
2727
}
2828
}
2929
}

src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs

+20-4
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,34 @@ public void RemoveResource(IResourceValue resource)
221221
_ResourceIds.Remove(resource.Id);
222222
}
223223

224-
public bool TryGetResource(string resourceId, out IResourceValue resource)
224+
/// <inheritdoc/>
225+
public bool TryGetResource(string nameOrResourceId, out IResourceValue resource)
225226
{
226-
if (_Symbols.TryGetValue(resourceId, out var symbol))
227-
resourceId = symbol.GetId(0);
227+
if (_Symbols.TryGetValue(nameOrResourceId, out var symbol))
228+
nameOrResourceId = symbol.GetId(0);
228229

229-
if (_ResourceIds.TryGetValue(resourceId, out resource))
230+
if (_ResourceIds.TryGetValue(nameOrResourceId, out resource))
230231
return true;
231232

232233
resource = null;
233234
return false;
234235
}
235236

237+
/// <inheritdoc/>
238+
public bool TryGetResourceCollection(string symbolicName, out IResourceValue[] resources)
239+
{
240+
resources = null;
241+
if (!_Symbols.TryGetValue(symbolicName, out var symbol) || symbol is not ArrayDeploymentSymbol array)
242+
return false;
243+
244+
var ids = array.GetIds();
245+
resources = new IResourceValue[ids.Length];
246+
for (var i = 0; i < ids.Length; i++)
247+
resources[i] = _ResourceIds[ids[i]];
248+
249+
return true;
250+
}
251+
236252
public void AddOutput(string name, JObject output)
237253
{
238254
if (string.IsNullOrEmpty(name) || output == null || _CurrentDeployment == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
// Test case for https://github.com/Azure/PSRule.Rules.Azure/issues/2922
5+
6+
module child_loop './Tests.Bicep.1.child.bicep' = [
7+
for (item, index) in range(0, 2): {
8+
name: 'deploy-${index}'
9+
params: {
10+
index: index
11+
}
12+
}
13+
]
14+
15+
output items array = map(child_loop, item => item.outputs.childValue)
16+
output itemsAsString string[] = map(child_loop, item => item.outputs.childValue)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
param index int
5+
6+
output childValue string = 'child-${index}'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3+
"languageVersion": "2.0",
4+
"contentVersion": "1.0.0.0",
5+
"metadata": {
6+
"_generator": {
7+
"name": "bicep",
8+
"version": "0.28.1.47646",
9+
"templateHash": "1381069533852459453"
10+
}
11+
},
12+
"resources": {
13+
"child_loop": {
14+
"copy": {
15+
"name": "child_loop",
16+
"count": "[length(range(0, 2))]"
17+
},
18+
"type": "Microsoft.Resources/deployments",
19+
"apiVersion": "2022-09-01",
20+
"name": "[format('deploy-{0}', copyIndex())]",
21+
"properties": {
22+
"expressionEvaluationOptions": {
23+
"scope": "inner"
24+
},
25+
"mode": "Incremental",
26+
"parameters": {
27+
"index": {
28+
"value": "[copyIndex()]"
29+
}
30+
},
31+
"template": {
32+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
33+
"languageVersion": "2.0",
34+
"contentVersion": "1.0.0.0",
35+
"metadata": {
36+
"_generator": {
37+
"name": "bicep",
38+
"version": "0.28.1.47646",
39+
"templateHash": "15231390219392886169"
40+
}
41+
},
42+
"parameters": {
43+
"index": {
44+
"type": "int"
45+
}
46+
},
47+
"resources": {},
48+
"outputs": {
49+
"childValue": {
50+
"type": "string",
51+
"value": "[format('child-{0}', parameters('index'))]"
52+
}
53+
}
54+
}
55+
}
56+
}
57+
},
58+
"outputs": {
59+
"items": {
60+
"type": "array",
61+
"value": "[map(references('child_loop'), lambda('item', lambdaVariables('item').outputs.childValue.value))]"
62+
},
63+
"itemsAsString": {
64+
"type": "array",
65+
"items": {
66+
"type": "string"
67+
},
68+
"value": "[map(references('child_loop'), lambda('item', lambdaVariables('item').outputs.childValue.value))]"
69+
}
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"experimentalFeaturesEnabled": {
3+
"optionalModuleNames": true,
4+
"symbolicNameCodegen": true
5+
}
6+
}

tests/PSRule.Rules.Azure.Tests/FunctionTests.cs

+24
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Globalization;
88
using System.IO;
99
using System.Linq;
10+
using System.Security.Cryptography.Xml;
1011
using Newtonsoft.Json;
1112
using Newtonsoft.Json.Linq;
1213
using PSRule.Rules.Azure.Configuration;
@@ -810,6 +811,29 @@ public void Reference()
810811
Assert.Equal("a", actual["name"].Value<string>());
811812
}
812813

814+
[Fact]
815+
[Trait(TRAIT, TRAIT_RESOURCE)]
816+
public void References()
817+
{
818+
var context = GetContext();
819+
var resourceId = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Unit.Test/type/a";
820+
var resource = new ResourceValue(resourceId, "a-0", "Unit.Test/type", "child_loop[0]", new JObject(), null);
821+
context.AddResource(resource);
822+
823+
var symbol = DeploymentSymbol.NewArray("child_loop");
824+
symbol.Configure(resource);
825+
context.AddSymbol(symbol);
826+
827+
var actual = (Functions.References(context, new object[] { "child_loop" }) as object[]).OfType<Mock.MockObject>().ToArray();
828+
Assert.NotNull(actual);
829+
830+
actual = (Functions.References(context, new object[] { "child_loop", "Full" }) as object[]).OfType<Mock.MockObject>().ToArray();
831+
Assert.NotNull(actual);
832+
833+
Assert.Throws<ExpressionArgumentException>(() => Functions.References(context, null));
834+
Assert.Throws<ExpressionArgumentException>(() => Functions.References(context, new object[] { "Unit.Test/type", "test" }));
835+
}
836+
813837
[Fact]
814838
[Trait(TRAIT, TRAIT_RESOURCE)]
815839
public void ResourceId()

tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
<None Update="Tests.Bicep.40.json">
261261
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
262262
</None>
263+
<None Update="Bicep\SymbolicNameTestCases\Tests.Bicep.1.json">
264+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
265+
</None>
263266
<None Update="Tests.Bicep.5.json">
264267
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
265268
</None>

0 commit comments

Comments
 (0)