Skip to content

Commit

Permalink
Fixed output map expansion with resource IDs Azure#3153 (Azure#3156)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Oct 31, 2024
1 parent dc51030 commit 4c81900
Show file tree
Hide file tree
Showing 13 changed files with 1,241 additions and 31 deletions.
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ What's changed since v1.39.3:
[#3085](https://github.com/Azure/PSRule.Rules.Azure/issues/3085)
- Quality updates to rule documentation by @BernieWhite.
[#3102](https://github.com/Azure/PSRule.Rules.Azure/issues/3102)
- Bug fixes:
- Fixed output map expansion with resource IDs by @BernieWhite.
[#3153](https://github.com/Azure/PSRule.Rules.Azure/issues/3153)

## v1.39.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private string GetId()
/// </summary>
private string GetName()
{
if (!Value.TryResourceName(out var name) || string.IsNullOrEmpty(name)) throw new TemplateSymbolException(SymbolicName);
if (!Value.TryNameProperty(out var name) || string.IsNullOrEmpty(name)) throw new TemplateSymbolException(SymbolicName);

return _Name = ExpandString(_Context, name);
}
Expand Down
7 changes: 7 additions & 0 deletions src/PSRule.Rules.Azure/Data/Template/ExpressionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,13 @@ internal static bool TryArray(object o, out Array value)
value = GetLongArray(o);
return true;
}
else if (o is Mock.MockUnknownObject mockObject && mockObject.Count == 0 && mockObject.TryMutateTo(TypePrimitive.Array, out var replaced) && replaced is JArray jArrayMock)
{
var jr = new JToken[jArrayMock.Count];
jArrayMock.CopyTo(jr, 0);
value = jr;
return true;
}
return false;
}

Expand Down
10 changes: 9 additions & 1 deletion src/PSRule.Rules.Azure/Data/Template/Functions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -739,12 +739,16 @@ internal static object Union(ITemplateContext context, object[] args)
throw ArgumentsOutOfRange(nameof(Union), args);

var hasMocks = false;
var mockArrays = 0;

// Find first non-null case.
for (var i = 0; i < args.Length; i++)
{
if (args[i] is IMock)
if (args[i] is IMock mock)
{
if (mock.BaseType == TypePrimitive.Array)
mockArrays++;

hasMocks = true;
continue;
}
Expand All @@ -758,6 +762,10 @@ internal static object Union(ITemplateContext context, object[] args)
return ExpressionHelpers.UnionObject(args, deepMerge: true);
}

// Handle mocks as arrays.
if (hasMocks && mockArrays == args.Length)
return ExpressionHelpers.UnionArray(args);

// Handle mocks as objects if no other object or array is found.
return hasMocks ? ExpressionHelpers.UnionObject(args, deepMerge: true) : null;
}
Expand Down
35 changes: 25 additions & 10 deletions src/PSRule.Rules.Azure/Data/Template/ParameterType.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

namespace PSRule.Rules.Azure.Data.Template;

internal readonly struct ParameterType
#nullable enable

[DebuggerDisplay("{Type}/{ItemType}, {Name}")]
internal readonly struct ParameterType(TypePrimitive type = TypePrimitive.None, TypePrimitive itemType = TypePrimitive.None, string? name = null)
{
public readonly TypePrimitive Type;
public readonly string Name;
public readonly TypePrimitive Type = type;
public readonly TypePrimitive ItemType = itemType;
public readonly string? Name = name;

public static readonly ParameterType String = new(TypePrimitive.String);
public static readonly ParameterType Object = new(TypePrimitive.Object);
public static readonly ParameterType SecureString = new(TypePrimitive.SecureString);
public static readonly ParameterType Array = new(TypePrimitive.Array);

public ParameterType(TypePrimitive type = TypePrimitive.None, string name = null)
public static bool TrySimpleType(string? type, out ParameterType? value)
{
Type = type;
Name = name;
if (!TypeHelpers.TryTypePrimitive(type, out var primitive) || primitive == null)
{
value = new ParameterType();
return false;
}
value = new ParameterType(primitive.Value, default, default);
return true;
}

public static bool TrySimpleType(string type, out ParameterType value)
public static bool TryArrayType(string? type, string? itemType, out ParameterType? value)
{
if (!Enum.TryParse(type, ignoreCase: true, result: out TypePrimitive primitive))
if (!TypeHelpers.TryTypePrimitive(type, out var primitive) || primitive == null)
{
value = new ParameterType();
return false;
}
value = new ParameterType(primitive, null);

value = TypeHelpers.TryTypePrimitive(itemType, out var itemPrimitive) && itemPrimitive != null
? new ParameterType(primitive.Value, itemPrimitive.Value, default)
: new ParameterType(primitive.Value, TypePrimitive.None, default);

return true;
}
}

#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class RuleDataExportVisitor : TemplateVisitor
private const string PROPERTY_RESOURCES = "resources";
private const string PROPERTY_ID = "id";
private const string PROPERTY_NAME = "name";
private const string PROPERTY_CUSTOM_NETWORK_INTERFACE_NAME = "customNetworkInterfaceName";
private const string PROPERTY_PROPERTIES = "properties";
private const string PROPERTY_CLIENT_ID = "clientId";
private const string PROPERTY_PRINCIPAL_ID = "principalId";
Expand Down Expand Up @@ -201,9 +202,13 @@ private static bool ProjectPrivateEndpoints(TemplateContext context, IResourceVa
// Add network interfaces
if (!properties.ContainsKeyInsensitive(PROPERTY_NETWORKINTERFACES))
{
// If the special case of a customNetworkInterfaceName property exists then use that for the name of the NIC.
var networkInterfaceName = properties.TryStringProperty(PROPERTY_CUSTOM_NETWORK_INTERFACE_NAME, out var customNetworkInterfaceName) &&
!string.IsNullOrEmpty(customNetworkInterfaceName) ? customNetworkInterfaceName : $"pe.nic.{ExpressionHelpers.GetUniqueString([resource.Id])}";

var networkInterface = new JObject
{
[PROPERTY_ID] = ResourceHelper.CombineResourceId(subscriptionId, resourceGroupName, TYPE_NETWORKINTERFACE, $"pe.nic.{ExpressionHelpers.GetUniqueString(new object[] { resource.Id })}")
[PROPERTY_ID] = ResourceHelper.CombineResourceId(subscriptionId, resourceGroupName, TYPE_NETWORKINTERFACE, networkInterfaceName)
};
properties[PROPERTY_NETWORKINTERFACES] = new JArray(new JObject[] { networkInterface });
}
Expand Down
81 changes: 67 additions & 14 deletions src/PSRule.Rules.Azure/Data/Template/TemplateVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -821,15 +821,18 @@ public object GetValue()
}
}

[DebuggerDisplay("{_Name}, {_Type.Type}")]
private sealed class LazyOutput : ILazyValue
{
private readonly string _Name;
private readonly TemplateContext _Context;
private readonly JObject _Value;
private readonly ParameterType _Type;

public LazyOutput(TemplateContext context, string name, JObject value)
{
_Context = context;
_Name = name;
_Value = value;
if (!TryParameterType(context, value, out var type))
throw ThrowTemplateOutputException(name);
Expand All @@ -840,6 +843,32 @@ public LazyOutput(TemplateContext context, string name, JObject value)
public object GetValue()
{
ResolveProperty(_Context, _Value, PROPERTY_VALUE);

// Handle basic types.
if (_Value.TryValueProperty(out var value) && _Type.Type == TypePrimitive.String && value.Type == JTokenType.String)
{
return _Value;
}
else if (value != null && _Type.Type == TypePrimitive.Bool && value.Type == JTokenType.Boolean)
{
return _Value;
}
else if (value != null && _Type.Type == TypePrimitive.Int && value.Type == JTokenType.Integer)
{
return _Value;
}
else if (value != null && _Type.Type == TypePrimitive.Array && _Type.ItemType == TypePrimitive.String && value.Type == JTokenType.Array)
{
return _Value;
}
else if (value != null && _Type.Type == TypePrimitive.Array && _Type.ItemType == TypePrimitive.Bool && value.Type == JTokenType.Array)
{
return _Value;
}
else if (value != null && _Type.Type == TypePrimitive.Array && _Type.ItemType == TypePrimitive.Int && value.Type == JTokenType.Array)
{
return _Value;
}
return new MockObject(_Value);
}
}
Expand Down Expand Up @@ -1050,25 +1079,37 @@ private static bool FillParameterWhenNullable(TemplateContext context, string pa
return true;
}

#nullable enable

private static bool TryParameterType(ITemplateContext context, JObject parameter, out ParameterType? value)
{
value = null;
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));

// Try $ref
// Try $ref property.
if (parameter.TryGetProperty(PROPERTY_REF, out var type) &&
context.TryDefinition(type, out var definition))
value = new ParameterType(definition.Type, type);

// Try type
else if (parameter.TryResourceType(out type) &&
ParameterType.TrySimpleType(type, out var v))
value = v;
{
value = new ParameterType(definition.Type, default, type);
}
// Try type property.
else if (parameter.TryTypeProperty(out type) && TypeHelpers.TryTypePrimitive(type, out var typePrimitive))
{
value = typePrimitive == TypePrimitive.Array && parameter.TryItemsTypeProperty(out var itemsType) ?
ParameterType.TryArrayType(type, itemsType, out var arrayType) ?
arrayType :
default :
ParameterType.TrySimpleType(type, out var typeSimple) ?
typeSimple :
default;
}

return value != null && value.Value.Type != TypePrimitive.None;
}

#nullable restore

private static void AddParameterFromType(TemplateContext context, string parameterName, ParameterType type, JToken value)
{
if (type.Type == TypePrimitive.Bool)
Expand Down Expand Up @@ -1200,7 +1241,7 @@ private static void Reference(TemplateContext context, string symbolicName, JObj
if (!condition)
continue;

if (!resource.TryResourceType(out var resourceType))
if (!resource.TryTypeProperty(out var resourceType))
throw new Exception();

var r = new ExistingResourceValue(context, resourceType, symbolicName, instance, copyIndex.Clone());
Expand Down Expand Up @@ -1255,8 +1296,8 @@ private static ResourceValue ResourceInstance(TemplateContext context, JObject r
if (resource.TryGetProperty<JArray>(PROPERTY_DEPENDSON, out var dependsOn))
resource[PROPERTY_DEPENDSON] = ExpandArray(context, dependsOn);

resource.TryResourceName(out var name);
resource.TryResourceType(out var type);
resource.TryNameProperty(out var name);
resource.TryTypeProperty(out var type);

var deploymentScope = GetDeploymentScope(context, resource, type, out var managementGroup, out var subscriptionId, out var resourceGroupName);

Expand Down Expand Up @@ -1346,8 +1387,12 @@ private static DeploymentScope GetDeploymentScope(TemplateContext context, JObje
private static string ResolveDeploymentScopeProperty(TemplateContext context, JObject resource, string propertyName, string contextValue)
{
var resolvedValue = contextValue;
if (resource.TryGetProperty<JValue>(propertyName, out var value) && value.Type != JTokenType.Null)
resolvedValue = ResolveToken(context, value).Value<string>();
if (resource.TryGetProperty<JValue>(propertyName, out var value) && value.Type != JTokenType.Null && ExpressionHelpers.TryString(value, out var s))
{
s = ExpandString(context, s);
if (!string.IsNullOrEmpty(s))
resolvedValue = s;
}

resource[propertyName] = resolvedValue;
return resolvedValue;
Expand Down Expand Up @@ -1449,10 +1494,18 @@ private TemplateContext GetDeploymentContext(TemplateContext context, string dep
var managementGroup = new ManagementGroupOption(context.ManagementGroup);
var parameterDefaults = new ParameterDefaultsOption(context.ParameterDefaults);
if (TryStringProperty(resource, PROPERTY_SUBSCRIPTIONID, out var subscriptionId))
subscription.SubscriptionId = ExpandString(context, subscriptionId);
{
var targetSubscriptionId = ExpandString(context, subscriptionId);
if (!string.IsNullOrEmpty(subscriptionId))
subscription.SubscriptionId = targetSubscriptionId;
}

if (TryStringProperty(resource, PROPERTY_RESOURCEGROUP, out var resourceGroupName))
resourceGroup.Name = ExpandString(context, resourceGroupName);
{
var targetResourceGroup = ExpandString(context, resourceGroupName);
if (!string.IsNullOrEmpty(targetResourceGroup))
resourceGroup.Name = targetResourceGroup;
}

resourceGroup.SubscriptionId = subscription.SubscriptionId;
TryObjectProperty(template, PROPERTY_PARAMETERS, out var templateParameters);
Expand Down
24 changes: 24 additions & 0 deletions src/PSRule.Rules.Azure/Data/Template/TypeHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace PSRule.Rules.Azure.Data.Template;

#nullable enable

internal static class TypeHelpers
{
public static bool TryTypePrimitive(string? type, out TypePrimitive? value)
{
if (string.IsNullOrEmpty(type) || !Enum.TryParse(type, ignoreCase: true, result: out TypePrimitive primitive))
{
value = default;
return false;
}
value = primitive;
return true;
}
}

#nullable restore
Loading

0 comments on commit 4c81900

Please sign in to comment.