Skip to content

Commit e8b0f1f

Browse files
authored
Refactor of Deployment cmdlets base class. (#24159)
* Refactor of DeploymentCmdletBase. This refactor is mostly concerning the verbosity of dynamic parameter logic within the cmdlet. This was broken up into the already existing TemplateUtility and the newly created ParameterUtility. Documentation was also updated and other pieces of code broken up. An additional purpose of this PR was to allow the deployment stack cmdlets to have an easier time implementing dynamic parameters. * added changelog addition. * Removed incorrect checks in existing test cases after changes.
1 parent d35ba08 commit e8b0f1f

File tree

7 files changed

+623
-428
lines changed

7 files changed

+623
-428
lines changed

src/Resources/ResourceManager/Implementation/CmdletBase/DeploymentCmdletBase.cs

Lines changed: 141 additions & 260 deletions
Large diffs are not rendered by default.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels;
2+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
6+
namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
7+
{
8+
/// <summary>
9+
/// For use in Deployment and DeploymentStack cmdlets with parameter conversion and operation.
10+
/// </summary>
11+
public static class ParameterUtility
12+
{
13+
/// <summary>
14+
/// Adds parameters to parameterObject hashtable.
15+
/// </summary>
16+
public static void AddTemplateFileParametersToHashtable(IReadOnlyDictionary<string, TemplateParameterFileParameter> parameters, Hashtable parameterObject)
17+
{
18+
parameters.ForEach(dp =>
19+
{
20+
var parameter = new Hashtable();
21+
if (dp.Value.Value != null)
22+
{
23+
parameter.Add("value", dp.Value.Value);
24+
}
25+
if (dp.Value.Reference != null)
26+
{
27+
parameter.Add("reference", dp.Value.Reference);
28+
}
29+
30+
parameterObject[dp.Key] = parameter;
31+
});
32+
}
33+
34+
/// <summary>
35+
/// Add parameters within TemplateObject to paramterObject hashtable,
36+
/// while accounting for special handling for KeyVault references.
37+
/// </summary>
38+
public static void AddTemplateObjectParametersToHashtable(Hashtable templateParameterObject, Hashtable parameterObject)
39+
{
40+
foreach (var parameterKey in templateParameterObject.Keys)
41+
{
42+
// Let default behavior of a value parameter if not a KeyVault reference Hashtable:
43+
var hashtableParameter = templateParameterObject[parameterKey] as Hashtable;
44+
if (hashtableParameter != null && hashtableParameter.ContainsKey("reference"))
45+
{
46+
parameterObject[parameterKey] = templateParameterObject[parameterKey];
47+
}
48+
else
49+
{
50+
parameterObject[parameterKey] = new Hashtable { { "value", templateParameterObject[parameterKey] } };
51+
}
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Converts bicep file parameters into format that matches TemplateParameterObject.
57+
/// </summary>
58+
public static Hashtable RestructureBicepParameters(IDictionary<string, TemplateParameterFileParameter> bicepparamFileParameters)
59+
{
60+
// The TemplateParameterObject property expects parameters to be in a different format to the parameters file JSON.
61+
// Here we convert from { "foo": { "value": "blah" } } to { "foo": "blah" }
62+
// with the exception of KV secret references which are left as { "foo": { "reference": ... } }
63+
var parameters = new Hashtable();
64+
if (bicepparamFileParameters == null) return parameters;
65+
66+
foreach (var paramName in bicepparamFileParameters.Keys)
67+
{
68+
var param = bicepparamFileParameters[paramName];
69+
if (param.Value != null)
70+
{
71+
parameters[paramName] = param.Value;
72+
}
73+
if (param.Reference != null)
74+
{
75+
var parameter = new Hashtable();
76+
parameter.Add("reference", param.Reference);
77+
parameters[paramName] = parameter;
78+
}
79+
}
80+
81+
return parameters;
82+
}
83+
}
84+
}

src/Resources/ResourceManager/Utilities/TemplateUtility.cs

Lines changed: 122 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15-
using Microsoft.Azure.Commands.Common.Authentication;
1615
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels;
1716
using Microsoft.WindowsAzure.Commands.Utilities.Common;
1817
using Newtonsoft.Json;
1918
using ProjectResources = Microsoft.Azure.Commands.ResourceManager.Cmdlets.Properties.Resources;
19+
using Microsoft.Azure.Management.Resources.Models;
20+
using System.Net;
2021
using System;
2122
using System.Collections;
2223
using System.Collections.Generic;
@@ -25,28 +26,61 @@
2526
using System.Management.Automation;
2627
using System.Diagnostics;
2728
using System.Security;
28-
using Microsoft.WindowsAzure.Commands.Common;
2929
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
3030
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Extensions;
3131
using Newtonsoft.Json.Linq;
32-
using System.Threading;
32+
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Components;
33+
using Microsoft.Azure.Management.Resources;
3334

3435
namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
3536
{
3637
public static class TemplateUtility
3738
{
38-
/// <summary>
39-
/// Gets the parameters for a given template file.
40-
/// </summary>
41-
/// <param name="templateFilePath">The gallery template path (local or remote)</param>
42-
/// <param name="templateParameterObject">Existing template parameter object</param>
43-
/// <param name="templateParameterFilePath">Path to the template parameter file if present</param>
44-
/// <param name="staticParameters">The existing PowerShell cmdlet parameters</param>
45-
/// <returns>The template parameters</returns>
46-
public static RuntimeDefinedParameterDictionary GetTemplateParametersFromFile(string templateFilePath, Hashtable templateParameterObject, string templateParameterFilePath, string[] staticParameters)
39+
40+
public static string ExtractTemplateContent(
41+
string templateFile,
42+
string templateUri,
43+
string templateSpecId,
44+
ITemplateSpecsClient templateSpecsClient,
45+
Hashtable templateObject
46+
)
4747
{
4848
string templateContent = null;
49+
if (templateObject != null)
50+
{
51+
templateContent = GetTemplateContentFromHashtable(templateObject);
52+
}
53+
else if (!string.IsNullOrEmpty(templateFile) || !string.IsNullOrEmpty(templateUri))
54+
{
55+
var file = !string.IsNullOrEmpty(templateFile) ? templateFile : templateUri;
56+
templateContent = GetTemplateContentFromFile(file);
57+
}
58+
else if (!string.IsNullOrEmpty(templateSpecId))
59+
{
60+
templateContent = GetTemplateContentFromTemplateSpec(templateSpecId, templateSpecsClient);
61+
}
62+
63+
return templateContent;
64+
}
65+
66+
public static Hashtable ExtractTemplateParameterContent(
67+
string templateParameterFile,
68+
string templateParameterUri
69+
)
70+
{
71+
Hashtable templateParameterContent = null;
72+
if (!string.IsNullOrEmpty(templateParameterFile) || !string.IsNullOrEmpty(templateParameterUri))
73+
{
74+
var file = !string.IsNullOrEmpty(templateParameterFile) ? templateParameterFile : templateParameterUri;
75+
templateParameterContent = GetTemplateParameterContentFromFile(file);
76+
}
4977

78+
return templateParameterContent;
79+
}
80+
81+
private static string GetTemplateContentFromFile(string templateFilePath)
82+
{
83+
string templateContent = null;
5084
if (templateFilePath != null)
5185
{
5286
if (Uri.IsWellFormedUriString(templateFilePath, UriKind.Absolute))
@@ -59,27 +93,80 @@ public static RuntimeDefinedParameterDictionary GetTemplateParametersFromFile(st
5993
}
6094
}
6195

62-
RuntimeDefinedParameterDictionary dynamicParameters = ParseTemplateAndExtractParameters(templateContent, templateParameterObject, templateParameterFilePath, staticParameters);
63-
64-
return dynamicParameters;
65-
}
66-
67-
public static RuntimeDefinedParameterDictionary GetTemplateParametersFromFile(object template, Hashtable templateParameterObject, string templateParameterFilePath, string[] staticParameters)
68-
{
69-
return ParseTemplateAndExtractParameters(template.ToString(), templateParameterObject, templateParameterFilePath, staticParameters);
96+
return templateContent;
7097
}
7198

72-
public static RuntimeDefinedParameterDictionary GetTemplateParametersFromFile(Hashtable templateObject, Hashtable templateParameterObject, string templateParameterFilePath, string[] staticParameters)
99+
private static string GetTemplateContentFromHashtable(Hashtable templateObject)
73100
{
74101
string templateContent = null;
75102
if (templateObject != null)
76103
{
77104
templateContent = JsonConvert.SerializeObject(templateObject);
78105
}
106+
107+
return templateContent;
108+
}
79109

80-
RuntimeDefinedParameterDictionary dynamicParameters = ParseTemplateAndExtractParameters(templateContent, templateParameterObject, templateParameterFilePath, staticParameters);
110+
private static string GetTemplateContentFromTemplateSpec(string templateSpecId, ITemplateSpecsClient client)
111+
{
112+
ResourceIdentifier resourceIdentifier = new ResourceIdentifier(templateSpecId);
113+
if (!resourceIdentifier.ResourceType.Equals("Microsoft.Resources/templateSpecs/versions", StringComparison.OrdinalIgnoreCase))
114+
{
115+
throw new PSArgumentException("No version found in Resource ID");
116+
}
81117

82-
return dynamicParameters;
118+
if (!string.IsNullOrEmpty(resourceIdentifier.Subscription) &&
119+
client.SubscriptionId != resourceIdentifier.Subscription)
120+
{
121+
// The template spec is in a different subscription than our default
122+
// context. Force the client to use that subscription:
123+
client.SubscriptionId = resourceIdentifier.Subscription;
124+
}
125+
try
126+
{
127+
var templateSpecVersion = client.TemplateSpecVersions.Get(
128+
ResourceIdUtility.GetResourceGroupName(templateSpecId),
129+
ResourceIdUtility.GetResourceName(templateSpecId).Split('/')[0],
130+
resourceIdentifier.ResourceName);
131+
132+
if (!(templateSpecVersion.MainTemplate is JObject))
133+
{
134+
throw new InvalidOperationException("Unexpected type."); // Sanity check
135+
}
136+
var templateObj = (JObject)templateSpecVersion.MainTemplate;
137+
138+
return templateObj.ToString();
139+
}
140+
catch (TemplateSpecsErrorException e)
141+
{
142+
// If the templateSpec resourceID is pointing to a non existant resource
143+
if (!e.Response.StatusCode.Equals(HttpStatusCode.NotFound))
144+
{
145+
// Throw for any other error that is not due to a 404 for the template resource.
146+
throw;
147+
}
148+
149+
return null;
150+
}
151+
}
152+
153+
private static Hashtable GetTemplateParameterContentFromFile(string templateParameterFilePath)
154+
{
155+
Hashtable templateParameterContent = null;
156+
157+
if (templateParameterFilePath != null)
158+
{
159+
if (Uri.IsWellFormedUriString(templateParameterFilePath, UriKind.Absolute))
160+
{
161+
templateParameterContent = new Hashtable(ParseTemplateParameterContent(templateParameterFilePath));
162+
}
163+
else if (FileUtilities.DataStore.FileExists(templateParameterFilePath))
164+
{
165+
templateParameterContent = new Hashtable(ParseTemplateParameterFileContents(templateParameterFilePath));
166+
}
167+
}
168+
169+
return templateParameterContent;
83170
}
84171

85172
public static Dictionary<string, TemplateParameterFileParameter> ParseTemplateParameterFileContents(string templateParameterFilePath)
@@ -146,14 +233,14 @@ public static Dictionary<string, TemplateParameterFileParameter> ParseTemplatePa
146233
return parameters;
147234
}
148235

149-
private static RuntimeDefinedParameterDictionary ParseTemplateAndExtractParameters(string templateContent, Hashtable templateParameterObject, string templateParameterFilePath, string[] staticParameters)
236+
public static RuntimeDefinedParameterDictionary GetDynamicParameters(string templateContent, Hashtable templateParameterObject, string[] staticParameters)
150237
{
151238
RuntimeDefinedParameterDictionary dynamicParameters = new RuntimeDefinedParameterDictionary();
152239

240+
// If the template content is not null, parameters should be extracted into dynamic parameters:
153241
if (!string.IsNullOrEmpty(templateContent))
154242
{
155-
TemplateFile templateFile = null;
156-
243+
TemplateFile templateFile;
157244
try
158245
{
159246
templateFile = templateContent.FromJson<TemplateFile>();
@@ -175,41 +262,34 @@ private static RuntimeDefinedParameterDictionary ParseTemplateAndExtractParamete
175262
dynamicParameters.Add(dynamicParameter.Name, dynamicParameter);
176263
}
177264
}
265+
178266
if (templateParameterObject != null)
179267
{
180-
UpdateParametersWithObject(staticParameters, dynamicParameters, templateParameterObject);
181-
}
182-
if (templateParameterFilePath != null && FileUtilities.DataStore.FileExists(templateParameterFilePath))
183-
{
184-
var parametersFromFile = ParseTemplateParameterFileContents(templateParameterFilePath);
185-
UpdateParametersWithObject(staticParameters, dynamicParameters, new Hashtable(parametersFromFile));
186-
}
187-
if (templateParameterFilePath != null && Uri.IsWellFormedUriString(templateParameterFilePath, UriKind.Absolute))
188-
{
189-
var parametersFromUri = ParseTemplateParameterContent(GeneralUtilities.DownloadFile(templateParameterFilePath));
190-
UpdateParametersWithObject(staticParameters, dynamicParameters, new Hashtable(parametersFromUri));
268+
SetDynamicParametersPassedInTemplateParameterObject(staticParameters, dynamicParameters, templateParameterObject);
191269
}
270+
192271
return dynamicParameters;
193272
}
194273

195-
private static void UpdateParametersWithObject(string[] staticParameters, RuntimeDefinedParameterDictionary dynamicParameters, Hashtable templateParameterObject)
274+
/// <summary>
275+
/// Sets the dynamic parameters that are defined in the passed in template parameter object, so that the user is not prompted on execution for a value.
276+
/// </summary>
277+
private static void SetDynamicParametersPassedInTemplateParameterObject(string[] staticParameters, RuntimeDefinedParameterDictionary dynamicParameters, Hashtable templateParameterObject)
196278
{
197279
const string duplicatedParameterSuffix = "FromTemplate";
198280

199281
if (templateParameterObject != null)
200282
{
201283
foreach (string paramName in templateParameterObject.Keys)
202284
{
285+
// The template parameters that clash with static parameter names will receive a suffix on their respective dynamic parameter:
203286
string dynamicParamName = staticParameters.Contains(paramName, StringComparer.OrdinalIgnoreCase)
204287
? paramName + duplicatedParameterSuffix
205288
: paramName;
206289

207290
if (dynamicParameters.TryGetValue(dynamicParamName, out RuntimeDefinedParameter dynamicParameter))
208291
{
209-
dynamicParameter.Value = templateParameterObject[paramName] is TemplateParameterFileParameter TemplateFileParameter
210-
? TemplateFileParameter.Value
211-
: templateParameterObject[paramName];
212-
292+
// Param exists in the template parameter object, so set it:
213293
dynamicParameter.IsSet = true;
214294
((ParameterAttribute)dynamicParameter.Attributes[0]).Mandatory = false;
215295
}

0 commit comments

Comments
 (0)