Skip to content

Commit 267fb26

Browse files
authored
Fixes object to hashtable conversion for default params Azure#3033 (Azure#3175)
1 parent 547e2b4 commit 267fb26

File tree

7 files changed

+723
-626
lines changed

7 files changed

+723
-626
lines changed

docs/CHANGELOG-v1.md

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
2929

3030
## Unreleased
3131

32+
What's changed since pre-release v1.40.0-B0103:
33+
34+
- Bug fixes:
35+
- Fixed object to hashtable conversion for default parameter values by @BernieWhite.
36+
[#3033](https://github.com/Azure/PSRule.Rules.Azure/issues/3033)
37+
3238
## v1.40.0-B0103 (pre-release)
3339

3440
What's changed since pre-release v1.40.0-B0063:

src/PSRule.Rules.Azure/Common/ResourceHelper.cs

+587-588
Large diffs are not rendered by default.

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

+52-17
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ internal TemplateContext()
119119
_Symbols = new Dictionary<string, IDeploymentSymbol>(StringComparer.OrdinalIgnoreCase);
120120
}
121121

122-
internal TemplateContext(PipelineContext context, SubscriptionOption subscription, ResourceGroupOption resourceGroup, TenantOption tenant, ManagementGroupOption managementGroup, ParameterDefaultsOption parameterDefaults)
122+
internal TemplateContext(PipelineContext context, SubscriptionOption subscription, ResourceGroupOption resourceGroup, TenantOption tenant, ManagementGroupOption managementGroup, IDictionary<string, object> parameterDefaults)
123123
: this()
124124
{
125125
Pipeline = context;
@@ -136,27 +136,27 @@ internal TemplateContext(PipelineContext context, SubscriptionOption subscriptio
136136
ManagementGroup = managementGroup;
137137

138138
if (parameterDefaults != null)
139-
ParameterDefaults = parameterDefaults;
139+
ParameterDefaults = new Dictionary<string, object>(parameterDefaults, StringComparer.OrdinalIgnoreCase);
140140
}
141141

142142
internal TemplateContext(PipelineContext context)
143143
: this()
144144
{
145145
Pipeline = context;
146146
if (context?.Option?.Configuration?.Subscription != null)
147-
Subscription = context?.Option?.Configuration?.Subscription;
147+
Subscription = context.Option.Configuration.Subscription;
148148

149149
if (context?.Option?.Configuration?.ResourceGroup != null)
150-
ResourceGroup = context?.Option?.Configuration?.ResourceGroup;
150+
ResourceGroup = context.Option.Configuration.ResourceGroup;
151151

152152
if (context?.Option?.Configuration?.Tenant != null)
153-
Tenant = context?.Option?.Configuration?.Tenant;
153+
Tenant = context.Option.Configuration.Tenant;
154154

155155
if (context?.Option?.Configuration?.ManagementGroup != null)
156-
ManagementGroup = context?.Option?.Configuration?.ManagementGroup;
156+
ManagementGroup = context.Option.Configuration.ManagementGroup;
157157

158158
if (context?.Option?.Configuration?.ParameterDefaults != null)
159-
ParameterDefaults = context?.Option?.Configuration?.ParameterDefaults;
159+
ParameterDefaults = new Dictionary<string, object>(context.Option.Configuration.ParameterDefaults, StringComparer.OrdinalIgnoreCase);
160160
}
161161

162162
private Dictionary<string, IParameterValue> Parameters { get; }
@@ -173,7 +173,7 @@ internal TemplateContext(PipelineContext context)
173173

174174
public ManagementGroupOption ManagementGroup { get; internal set; }
175175

176-
public ParameterDefaultsOption ParameterDefaults { get; private set; }
176+
public IDictionary<string, object> ParameterDefaults { get; private set; }
177177

178178
/// <inheritdoc/>
179179
public DeploymentValue Deployment => _Deployment.Count > 0 ? _Deployment.Peek() : null;
@@ -628,15 +628,51 @@ internal bool TryParameterAssignment(string parameterName, out JToken value)
628628
internal bool TryParameterDefault(string parameterName, ParameterType type, out JToken value)
629629
{
630630
value = default;
631-
return type.Type switch
631+
switch (type.Type)
632632
{
633-
TypePrimitive.String or TypePrimitive.SecureString => ParameterDefaults.TryGetString(parameterName, out value),
634-
TypePrimitive.Bool => ParameterDefaults.TryGetBool(parameterName, out value),
635-
TypePrimitive.Int => ParameterDefaults.TryGetLong(parameterName, out value),
636-
TypePrimitive.Array => ParameterDefaults.TryGetArray(parameterName, out value),
637-
TypePrimitive.Object or TypePrimitive.SecureObject => ParameterDefaults.TryGetObject(parameterName, out value),
638-
_ => false,
633+
case TypePrimitive.String:
634+
case TypePrimitive.SecureString:
635+
if (ParameterDefaults.TryGetString(parameterName, out var s))
636+
{
637+
value = new JValue(s);
638+
return true;
639+
}
640+
break;
641+
642+
case TypePrimitive.Bool:
643+
if (ParameterDefaults.TryGetBool(parameterName, out var b))
644+
{
645+
value = new JValue(b);
646+
return true;
647+
}
648+
break;
649+
650+
case TypePrimitive.Int:
651+
if (ParameterDefaults.TryGetLong(parameterName, out var i))
652+
{
653+
value = new JValue(i);
654+
return true;
655+
}
656+
break;
657+
658+
case TypePrimitive.Array:
659+
if (ParameterDefaults.TryGetArray(parameterName, out var a))
660+
{
661+
value = a;
662+
return true;
663+
}
664+
break;
665+
666+
case TypePrimitive.Object:
667+
case TypePrimitive.SecureObject:
668+
if (ParameterDefaults.TryGetObject(parameterName, out var o))
669+
{
670+
value = o;
671+
return true;
672+
}
673+
break;
639674
};
675+
return false;
640676
}
641677

642678
internal bool TryParameter(string parameterName)
@@ -1492,7 +1528,6 @@ private TemplateContext GetDeploymentContext(TemplateContext context, string dep
14921528
var resourceGroup = new ResourceGroupOption(context.ResourceGroup);
14931529
var tenant = new TenantOption(context.Tenant);
14941530
var managementGroup = new ManagementGroupOption(context.ManagementGroup);
1495-
var parameterDefaults = new ParameterDefaultsOption(context.ParameterDefaults);
14961531
if (TryStringProperty(resource, PROPERTY_SUBSCRIPTIONID, out var subscriptionId))
14971532
{
14981533
var targetSubscriptionId = ExpandString(context, subscriptionId);
@@ -1510,7 +1545,7 @@ private TemplateContext GetDeploymentContext(TemplateContext context, string dep
15101545
resourceGroup.SubscriptionId = subscription.SubscriptionId;
15111546
TryObjectProperty(template, PROPERTY_PARAMETERS, out var templateParameters);
15121547

1513-
var deploymentContext = new TemplateContext(context.Pipeline, subscription, resourceGroup, tenant, managementGroup, parameterDefaults);
1548+
var deploymentContext = new TemplateContext(context.Pipeline, subscription, resourceGroup, tenant, managementGroup, context.ParameterDefaults);
15141549

15151550
// Handle custom type definitions early to allow type mapping of parameters if required.
15161551
if (TryObjectProperty(template, PROPERTY_DEFINITIONS, out var definitions))

src/PSRule.Rules.Azure/DictionaryExtensions.cs

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

4+
using Newtonsoft.Json.Linq;
45
using System.Collections;
56
using System.Collections.Generic;
67
using System.Diagnostics;
@@ -82,12 +83,14 @@ public static bool TryPopBool(this IDictionary<string, object> dictionary, strin
8283
public static bool TryGetString(this IDictionary<string, object> dictionary, string key, out string value)
8384
{
8485
value = null;
85-
if (!dictionary.TryGetValue(key, out var o))
86-
return false;
87-
88-
if (o is string result)
86+
if (dictionary.TryGetValue(key, out var o) && o is string s)
8987
{
90-
value = result;
88+
value = s;
89+
return true;
90+
}
91+
if (dictionary.TryGetValue(key, out s))
92+
{
93+
value = s;
9194
return true;
9295
}
9396
return false;
@@ -121,6 +124,26 @@ public static bool TryGetLong(this IDictionary<string, object> dictionary, strin
121124
return false;
122125
}
123126

127+
public static bool TryGetArray(this IDictionary<string, object> dictionary, string parameterName, out JToken value)
128+
{
129+
value = default;
130+
if (!dictionary.TryGetValue<List<object>>(parameterName, out var result))
131+
return false;
132+
133+
value = JArray.FromObject(result);
134+
return true;
135+
}
136+
137+
public static bool TryGetObject(this IDictionary<string, object> dictionary, string parameterName, out JToken value)
138+
{
139+
value = default;
140+
if (!dictionary.TryGetValue<Dictionary<object, object>>(parameterName, out var result))
141+
return false;
142+
143+
value = JObject.FromObject(result);
144+
return true;
145+
}
146+
124147
/// <summary>
125148
/// Add an item to the dictionary if it doesn't already exist in the dictionary.
126149
/// </summary>

src/PSRule.Rules.Azure/PSObjectExtensions.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,19 @@ internal static bool GetPath(this PSObject sourceObject, out string path)
4646
internal static Hashtable ToHashtable(this PSObject o)
4747
{
4848
var result = new Hashtable();
49-
foreach (var p in o.Properties)
49+
if (o.BaseObject is IDictionary d)
5050
{
51-
result[p.Name] = p.Value;
51+
foreach (var k in d.Keys)
52+
{
53+
result[k.ToString()] = d[k];
54+
}
55+
}
56+
else
57+
{
58+
foreach (var p in o.Properties)
59+
{
60+
result[p.Name] = p.Value;
61+
}
5262
}
5363
return result;
5464
}

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

+13-13
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,29 @@ public void TryResourceIdComponentsFromResourceId()
5353
"Microsoft.Network/virtualNetworks/vnet-A/subnets/GatewaySubnet"
5454
};
5555

56-
Assert.True(ResourceHelper.TryResourceIdComponents(id[0], out var subscriptionId, out var resourceGroupName, out string[] resourceTypeComponents, out string[] nameComponents));
56+
Assert.True(ResourceHelper.TryResourceIdComponents(id[0], out var subscriptionId, out var resourceGroupName, out string[]? resourceTypeComponents, out string[]? nameComponents));
5757
Assert.Equal("00000000-0000-0000-0000-000000000000", subscriptionId);
5858
Assert.Equal("rg-test", resourceGroupName);
59-
Assert.Equal("microsoft.operationalinsights/workspaces", resourceTypeComponents[0]);
60-
Assert.Equal("workspace001", nameComponents[0]);
59+
Assert.Equal("microsoft.operationalinsights/workspaces", resourceTypeComponents?[0]);
60+
Assert.Equal("workspace001", nameComponents?[0]);
6161

6262
Assert.True(ResourceHelper.TryResourceIdComponents(id[1], out subscriptionId, out resourceGroupName, out resourceTypeComponents, out nameComponents));
6363
Assert.Equal("ffffffff-ffff-ffff-ffff-ffffffffffff", subscriptionId);
6464
Assert.Equal("test-rg", resourceGroupName);
65-
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents[0]);
66-
Assert.Equal("subnets", resourceTypeComponents[1]);
67-
Assert.Equal("vnet-A", nameComponents[0]);
68-
Assert.Equal("GatewaySubnet", nameComponents[1]);
65+
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents?[0]);
66+
Assert.Equal("subnets", resourceTypeComponents?[1]);
67+
Assert.Equal("vnet-A", nameComponents?[0]);
68+
Assert.Equal("GatewaySubnet", nameComponents?[1]);
6969

7070
Assert.True(ResourceHelper.TryResourceIdComponents(id[2], out _, out _, out resourceTypeComponents, out nameComponents));
71-
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents[0]);
72-
Assert.Equal("vnet-A", nameComponents[0]);
71+
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents?[0]);
72+
Assert.Equal("vnet-A", nameComponents?[0]);
7373

7474
Assert.True(ResourceHelper.TryResourceIdComponents(id[3], out _, out _, out resourceTypeComponents, out nameComponents));
75-
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents[0]);
76-
Assert.Equal("subnets", resourceTypeComponents[1]);
77-
Assert.Equal("vnet-A", nameComponents[0]);
78-
Assert.Equal("GatewaySubnet", nameComponents[1]);
75+
Assert.Equal("Microsoft.Network/virtualNetworks", resourceTypeComponents?[0]);
76+
Assert.Equal("subnets", resourceTypeComponents?[1]);
77+
Assert.Equal("vnet-A", nameComponents?[0]);
78+
Assert.Equal("GatewaySubnet", nameComponents?[1]);
7979
}
8080

8181
[Fact]

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

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

4+
using System.Collections;
45
using System.Management.Automation;
56
using PSRule.Rules.Azure.Runtime;
67

@@ -89,7 +90,7 @@ public void WithAzureDeployment_WhenValid_ShouldSetOptions()
8990
}
9091

9192
[Fact]
92-
public void WithParameterDefaults_WhenValid_ShouldSetOptions()
93+
public void WithParameterDefaults_WhenValidPSObject_ShouldSetOptions()
9394
{
9495
var runtime = GetRuntimeService();
9596
var pso = new PSObject();
@@ -107,6 +108,29 @@ public void WithParameterDefaults_WhenValid_ShouldSetOptions()
107108
Assert.Equal("2", value2);
108109
}
109110

111+
[Fact]
112+
public void WithParameterDefaults_WhenValidHashtable_ShouldSetOptions()
113+
{
114+
var runtime = GetRuntimeService();
115+
var hashtable = new Hashtable
116+
{
117+
["value1"] = "1",
118+
["value2"] = "2"
119+
};
120+
121+
var pso = new PSObject(hashtable);
122+
123+
// Act
124+
runtime.WithParameterDefaults(pso);
125+
126+
// Assert
127+
var actual = runtime.ToPSRuleOption();
128+
Assert.True(actual.Configuration.ParameterDefaults.TryGetString("value1", out string value1));
129+
Assert.Equal("1", value1);
130+
Assert.True(actual.Configuration.ParameterDefaults.TryGetString("value2", out string value2));
131+
Assert.Equal("2", value2);
132+
}
133+
110134
#region Helper methods
111135

112136
private static RuntimeService GetRuntimeService()

0 commit comments

Comments
 (0)