Parameter and Variable Resolution (#323)

* separate out into method and call for dependson unpacking

* Initial code to extract from mulitiple "DependsOn" entries

* Rename method and vars and add some descriptive comments

* Updating the code to work with other code that will be introduced.

* Minor updates to comments and code.

* Minor update.

* Cleanup a lot and add more descriptive comments

* Some renaming cause i can never decide

* Remove launchSettings

* Remove no longer needed if

* Use default value key

* set resolvedvalue to null if defaultvalue does not exist

* resolve csharpier

* Some cleanup

* Simplify regex

* Potential solution for adding quotes to resolved values that do not contian arm template functions

* Typo with name

* Reuse square bracket pattern and remove parantheses regex

* Typo remove double and

---------

Co-authored-by: Marc Lerwick <marclerwick@microsoft.com>
This commit is contained in:
Mary Sha 2023-04-14 10:13:44 -07:00 коммит произвёл GitHub
Родитель 815402fb25
Коммит a1595f9626
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
1 изменённых файлов: 162 добавлений и 41 удалений

Просмотреть файл

@ -5,12 +5,21 @@ namespace Generators;
public class AzureDeploymentImporter
{
private static Regex s_parametersOrVariablesRegex = new Regex(
"(?<paramOrVarType>parameters|variables)\\('(?<paramOrVarName>.*?)'\\)",
RegexOptions.Compiled
);
private static Regex s_resourceIdParametersRegex = new Regex(
"\\[resourceId\\((?<resourceIdParameters>.*)\\)\\]",
RegexOptions.Compiled
);
private static string s_resourceIdParametersKey = "resourceIdParameters";
private static string s_dependsOnKey = "dependsOn";
private static string s_squareBracketPattern = "\\[(.*?)\\]";
private static string s_squareBracketSubstituition = "$1";
private static string s_paramOrVarNameGroupKey = "paramOrVarName";
private static string s_paramOrVarTypeGroupKey = "paramOrVarType";
private static string s_defaultValueKey = "defaultValue";
public static IEnumerable<TestMetadata> Import(FileInfo inputFile, string outputFolderPath)
{
@ -85,48 +94,14 @@ public class AzureDeploymentImporter
throw new Exception("Failed to parse json file");
}
if (resourceName.StartsWith("[") && resourceName.EndsWith("]"))
{
var parts = new[] { "parameters", "variables" };
foreach (var part in parts)
{
var temp = resourceName;
var parsedValue = temp!
.Replace("[", "")
.Replace("]", "")
.Replace(part, "")
.Replace("(", "")
.Replace(")", "")
.Replace("'", "")
.Trim();
if (!string.IsNullOrWhiteSpace(parsedValue))
{
var defaultValueNode = parsed[part]?[parsedValue];
if (defaultValueNode is JsonObject)
{
temp = defaultValueNode["defaultValue"]!.ToString();
}
else
{
temp = parsed[part]?[parsedValue]?.ToString();
}
}
if (!string.IsNullOrWhiteSpace(temp))
{
resourceName = temp;
break;
}
}
}
resourceName = ResolveParamsAndVariables(resourceName, parsed);
if (resourceName == null)
{
throw new Exception("Failed to parse json file");
}
var extraProperties = GetExtraProperties(resource);
var extraProperties = GetExtraProperties(resource, parsed);
try
{
@ -172,7 +147,10 @@ public class AzureDeploymentImporter
/// }
/// </code>
/// </summary>
private static Dictionary<string, string> GetExtraProperties(JsonNode resource)
private static Dictionary<string, string> GetExtraProperties(
JsonNode resource,
JsonObject armTemplateObject
)
{
var extraProperties = new Dictionary<string, string>();
var dependencies = (JsonArray?)resource[s_dependsOnKey];
@ -214,10 +192,15 @@ public class AzureDeploymentImporter
for (int index = 0; index < pathParts.Count(); index++)
{
// If the value is a hard coded value and not a "parameter" or "variable", then the
// value will be "'value'" so trim any single quotes (this will not affect "parameter"
// or "variable" entries).
extraProperties.Add(pathParts[index], values[index].Trim('\''));
// If the value is a "parameter" or "variable", then resolve it to the correct value
// of the parameter or variable. If the value is a hard coded value, then the
// value will be "'value'" so trim any single quotes.
var value = ResolveParamsAndVariables(
values[index],
armTemplateObject
)
.Trim('\'');
extraProperties.Add(pathParts[index], value);
}
}
}
@ -229,4 +212,142 @@ public class AzureDeploymentImporter
return extraProperties;
}
/// <summary>
/// Takes a string from an ARM template containing instances of <c>parameters('...')</c> and
/// <c>variables('...')</c> and resolves those instances to the correct values, if possible.
/// <example>
/// For example, if the following ARM template (<paramref name="armTemplateObject"/>) with the parameter block
/// below is passed in:
/// <code>
/// {...
/// "parameters": {
/// "demoParam": {
/// "type": "string",
/// "defaultValue": "Contoso"
/// }
/// }
/// ...}
/// </code>
/// and the following string (<paramref name ="stringToResolve"/>) is passed in:
/// <code>
/// "[parameters('demoParam')]"
/// </code>
/// The parameter will be resolved to the default value and result in the following return value:
/// <code>
/// "Contoso"
/// </code>
/// This method can also handle resolving ARM template variables. For example if the following ARM template
/// (<paramref name="armTemplateObject"/>) with the variable block below is passed in:
/// <code>
/// {...
/// "variables": {
/// "demoVar": "Contoso"
/// }
/// ...}
/// </code>
/// and the following string (<paramref name ="stringToResolve"/>) is passed in:
/// <code>
/// "[variables('demoVar')]"
/// </code>
/// The variable will be resolved to the correct value and result in the following return value:
/// <code>
/// "Contoso"
/// </code>
/// Finally, this method can also handle resolving a mixture of ARM template parameters and variables. For example
/// if the following ARM template (<paramref name="armTemplateObject"/>) with the parameter and variable block
/// below is passed in:
/// <code>
/// {...
/// "parameters": {
/// "demoParam": {
/// "type": "string",
/// "defaultValue": "ContosoParam"
/// }
/// }
/// "variables": {
/// "demoVar": "ContosoVar"
/// }
/// ...}
/// </code>
/// and the following string (<paramref name ="stringToResolve"/>) is passed in:
/// <code>
/// "[format('{0}{1}', variables('demoVar'), parameters('demoParam')]"
/// </code>
/// The variable and parameter will be resolved to the correct values and result in the following return value:
/// <code>
/// "format('{0}{1}', 'ContosoVar' , 'ContosoParam')"
/// </code>
/// </summary>
private static string ResolveParamsAndVariables(
string stringToResolve,
JsonObject armTemplateObject
)
{
// Find and remove square brackets from the parameter/variable string. Square brackets are specific to
// ARM template syntax and are not needed in generated tests.
stringToResolve = Regex.Replace(
stringToResolve,
s_squareBracketPattern,
s_squareBracketSubstituition
);
// Find all matches in the parameter/variable string that follows the pattern of "parameters('...')" or
// "variables('...')". The regular expression pattern defines two named subexpressions: paramOrVarType, which
// represents the type of parameter/variable (e.g., "parameters" or "variables"), and paramOrVarName, which
// represents the name of the parameter/variable.
var matches = s_parametersOrVariablesRegex.Matches(stringToResolve);
foreach (Match match in matches)
{
var name = match.Groups[s_paramOrVarNameGroupKey].Value;
var type = match.Groups[s_paramOrVarTypeGroupKey].Value;
var resolvedValue = System.String.Empty;
if (!string.IsNullOrWhiteSpace(name))
{
// ARM templates will contain a JSON Object representing parameters and variables for the ARM template.
// Get the correct JSON Object using the type from the regex match (e.g., "parameters" or "variables").
var parametersOrVariablesObj = armTemplateObject[type];
if (parametersOrVariablesObj != null)
{
// Get the value of the parameter/variable from the JSON Object. If the value is still a JSON
// Object, that means it is a parameter that has a default value, so get the default value (if it
// exists). Otherwise, the value can be converted to a string and assigned to resolveValue.
var resolvedValueNode = parametersOrVariablesObj[name];
if (resolvedValueNode != null)
{
if (resolvedValueNode is JsonObject)
{
resolvedValue = resolvedValueNode[s_defaultValueKey]?.ToString();
}
else
{
resolvedValue = resolvedValueNode.ToString();
}
}
}
}
if (!string.IsNullOrWhiteSpace(resolvedValue))
{
// If square brackets are present in the resolved value, remove them from the value. Square brackets
// are specific to ARM template syntax to represent expressions and are not needed in generated tests.
// If the resolved value does not have square brackets, then the resolved value does not contain ARM
// functions and should be wrapped in single quotes.
if (Regex.Match(resolvedValue, s_squareBracketPattern).Success)
{
resolvedValue = Regex.Replace(
resolvedValue,
s_squareBracketPattern,
s_squareBracketSubstituition
);
}
else
{
resolvedValue = "\'" + resolvedValue + "\'";
}
stringToResolve = stringToResolve.Replace(match.Value, resolvedValue);
}
}
return stringToResolve;
}
}