mirror of
https://github.com/actions/runner.git
synced 2025-12-16 15:06:49 +00:00
GitHub Actions Runner
This commit is contained in:
321
src/Sdk/DTWebApi/WebApi/VariableUtility.cs
Normal file
321
src/Sdk/DTWebApi/WebApi/VariableUtility.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
public static class VariableUtility
|
||||
{
|
||||
public static EnableAccessTokenType GetEnableAccessTokenType(IDictionary<String, VariableValue> variables)
|
||||
{
|
||||
EnableAccessTokenType type;
|
||||
if (variables != null &&
|
||||
variables.TryGetValue(WellKnownDistributedTaskVariables.EnableAccessToken, out VariableValue enableVariable) &&
|
||||
enableVariable != null)
|
||||
{
|
||||
Enum.TryParse(enableVariable.Value, true, out type);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = EnableAccessTokenType.None;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public static Boolean IsVariable(String value)
|
||||
{
|
||||
return s_variableReferenceRegex.Value.IsMatch(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces variables by recursively cloning tokens in a JObject or JArray by
|
||||
/// Walks tokens and uses ExpandVariables(string, vars) to resolve all string tokens
|
||||
/// </summary>
|
||||
/// <param name="token">root token must be a JObject or JArray</param>
|
||||
/// <param name="replacementDictionary">key value variables to replace in the $(xxx) format</param>
|
||||
/// <returns>root token of cloned tree</returns>
|
||||
public static JToken ExpandVariables(JToken token, IDictionary<string, string> replacementDictionary, bool useMachineVariables = true)
|
||||
{
|
||||
var mapFuncs = new Dictionary<JTokenType, Func<JToken, JToken>>
|
||||
{
|
||||
{
|
||||
JTokenType.String,
|
||||
(t) => VariableUtility.ExpandVariables(t.ToString(), replacementDictionary, useMachineVariables)
|
||||
}
|
||||
};
|
||||
|
||||
return token.Map(mapFuncs);
|
||||
}
|
||||
|
||||
public static JToken ExpandVariables(
|
||||
JToken token,
|
||||
VariablesDictionary additionalVariableReplacements,
|
||||
Boolean useMachineVariables)
|
||||
{
|
||||
return ExpandVariables(token, (IDictionary<String, String>)additionalVariableReplacements, useMachineVariables);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces multiple variable sets by recursively cloning tokens in a JObject or JArray.
|
||||
/// Walks tokens and uses ExpandVariables(string, vars) for each set of variables on all string tokens
|
||||
/// </summary>
|
||||
/// <param name="token">root token must be a JObject or JArray</param>
|
||||
/// <param name="replacementsList">list of replacement key value pairs in the $(xxx) format</param>
|
||||
/// <returns>root token of cloned tree</returns>
|
||||
public static JToken ExpandVariables(JToken token, IList<IDictionary<string, string>> replacementsList)
|
||||
{
|
||||
var mapFuncs = new Dictionary<JTokenType, Func<JToken, JToken>>
|
||||
{
|
||||
{
|
||||
JTokenType.String,
|
||||
(t) => replacementsList.Aggregate(t, (current, replacementVariables) => ExpandVariables(current.ToString(), replacementVariables))
|
||||
}
|
||||
};
|
||||
|
||||
return token.Map(mapFuncs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overload method for ExpandVariables
|
||||
/// Expand variables in the input provided using the dictionary and the machine's environment variables
|
||||
/// </summary>
|
||||
public static String ExpandVariables(String input, IDictionary<String, String> additionalVariableReplacements)
|
||||
{
|
||||
return ExpandVariables(input, additionalVariableReplacements, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces variable references of the form $(variable) with the corresponding replacement value. Values
|
||||
/// populated into the environment directly are used first. If no value is found in the automation environment
|
||||
/// then the machine environment variables will be used as a fall back.
|
||||
/// </summary>
|
||||
/// <param name="input">The value which should be analyzed for environment variables and updated accordingly</param>
|
||||
/// <param name="useMachineVariables">Use the machine's environment variables when it is true</param>
|
||||
/// <returns>A new value with all variables expanded to their current value based on the environment</returns>
|
||||
public static String ExpandVariables(String input, IDictionary<String, String> additionalVariableReplacements, bool useMachineVariables)
|
||||
{
|
||||
// Do a quick up-front check against a regular expression to determine whether or not there is any
|
||||
// reason to allocate memory to replace values in the input
|
||||
if (!s_variableReferenceRegex.Value.IsMatch(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input);
|
||||
List<String> referencedVariables = GetReferencedVariables(input);
|
||||
for (Int32 i = 0; i < referencedVariables.Count; i++)
|
||||
{
|
||||
// The variable reference is of the format $(variable), so we start at index 2 and cut off the last ')'
|
||||
// character by extracting a length of 3 less than the original length.
|
||||
String variableName = referencedVariables[i].Substring(2, referencedVariables[i].Length - 3);
|
||||
|
||||
String replacementValue;
|
||||
if (!additionalVariableReplacements.TryGetValue(variableName, out replacementValue) && useMachineVariables)
|
||||
{
|
||||
replacementValue = System.Environment.GetEnvironmentVariable(variableName);
|
||||
}
|
||||
|
||||
if (replacementValue != null)
|
||||
{
|
||||
sb.Replace(referencedVariables[i], replacementValue);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces variable references of the form $(variable) with the corresponding replacement value. Values
|
||||
/// populated into the environment directly are used first. If no value is found in the automation environment
|
||||
/// then the machine environment variables will be used as a fall back.
|
||||
/// </summary>
|
||||
/// <param name="input">The value which should be analyzed for environment variables and updated accordingly</param>
|
||||
/// <param name="useMachineVariables">Use the machine's environment variables when it is true</param>
|
||||
/// <returns>A new value with all variables expanded to their current value based on the environment</returns>
|
||||
public static String ExpandVariables(
|
||||
String input,
|
||||
VariablesDictionary additionalVariableReplacements,
|
||||
Boolean useMachineVariables,
|
||||
Boolean maskSecrets = false)
|
||||
{
|
||||
return ExpandVariables(input, (IDictionary<String, VariableValue>)additionalVariableReplacements, useMachineVariables, maskSecrets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces variable references of the form $(variable) with the corresponding replacement value. Values
|
||||
/// populated into the environment directly are used first. If no value is found in the automation environment
|
||||
/// then the machine environment variables will be used as a fall back.
|
||||
/// </summary>
|
||||
/// <param name="input">The value which should be analyzed for environment variables and updated accordingly</param>
|
||||
/// <param name="useMachineVariables">Use the machine's environment variables when it is true</param>
|
||||
/// <returns>A new value with all variables expanded to their current value based on the environment</returns>
|
||||
public static String ExpandVariables(
|
||||
String input,
|
||||
IDictionary<String, VariableValue> additionalVariableReplacements,
|
||||
Boolean useMachineVariables,
|
||||
Boolean maskSecrets = false)
|
||||
{
|
||||
if (String.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input);
|
||||
List<String> referencedVariables = GetReferencedVariables(input);
|
||||
for (Int32 i = 0; i < referencedVariables.Count; i++)
|
||||
{
|
||||
// The variable reference is of the format $(variable), so we start at index 2 and cut off the last ')'
|
||||
// character by extracting a length of 3 less than the original length.
|
||||
String variableName = referencedVariables[i].Substring(2, referencedVariables[i].Length - 3);
|
||||
|
||||
VariableValue replacementValue;
|
||||
if (!additionalVariableReplacements.TryGetValue(variableName, out replacementValue) && useMachineVariables)
|
||||
{
|
||||
replacementValue = new VariableValue { Value = System.Environment.GetEnvironmentVariable(variableName) };
|
||||
}
|
||||
|
||||
if (replacementValue != null)
|
||||
{
|
||||
var value = replacementValue.Value;
|
||||
if (replacementValue.IsSecret && maskSecrets)
|
||||
{
|
||||
value = "***";
|
||||
}
|
||||
|
||||
sb.Replace(referencedVariables[i], value);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces variable references of the form variables['variable_name'] with corresponding replacement values
|
||||
/// </summary>
|
||||
/// <param name="condition">Task condition</param>
|
||||
/// <param name="additionalVariableReplacements">List of variables and their replacement values</param>
|
||||
/// <returns></returns>
|
||||
public static String ExpandConditionVariables(String condition, IDictionary<String, String> additionalVariableReplacements, bool useMachineVariables)
|
||||
{
|
||||
// Do a quick up-front check against a regular expression to determine whether or not there is any
|
||||
// reason to allocate memory to replace values in the input
|
||||
if (!s_conditionVariableReferenceRegex.Value.IsMatch(condition))
|
||||
{
|
||||
return condition;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(condition);
|
||||
MatchCollection matches = s_conditionVariableReferenceRegex.Value.Matches(condition);
|
||||
|
||||
for (Int32 i = 0; i < matches.Count; i++)
|
||||
{
|
||||
if (matches[i].Length != 0 && matches[i].Groups.Count >= 2)
|
||||
{
|
||||
String referencedVariable = matches[i].Groups[0].Value;
|
||||
String variableName = matches[i].Groups[1].Value;
|
||||
|
||||
String replacementValue;
|
||||
if (!additionalVariableReplacements.TryGetValue(variableName, out replacementValue) && useMachineVariables)
|
||||
{
|
||||
replacementValue = System.Environment.GetEnvironmentVariable(variableName);
|
||||
}
|
||||
|
||||
if (replacementValue != null)
|
||||
{
|
||||
string convertedValue = PrepareReplacementStringForConditions(replacementValue);
|
||||
sb.Replace(referencedVariable, convertedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare replacement string from the given input. For a normal input, add ' around it.
|
||||
/// Convert a variable of format ${var} to variables['var'] to suit custom conditions
|
||||
/// </summary>
|
||||
/// <param name="replacementValue">input replacement value</param>
|
||||
/// <returns></returns>
|
||||
public static String PrepareReplacementStringForConditions(String replacementValue)
|
||||
{
|
||||
if (replacementValue == null || !IsVariable(replacementValue))
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, c_conditionReplacementFormat, replacementValue);
|
||||
}
|
||||
|
||||
List<String> variables = GetReferencedVariables(replacementValue);
|
||||
|
||||
if (variables.Count != 1 || replacementValue.Trim() != variables[0])
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, c_conditionReplacementFormat, replacementValue);
|
||||
}
|
||||
|
||||
// Start from index 2 [after $( ] and continue till last but one
|
||||
string variableName = variables[0].Substring(2, variables[0].Length - 3);
|
||||
return string.Format(CultureInfo.InvariantCulture, c_conditionVariableFormat, variableName);
|
||||
}
|
||||
|
||||
private static List<String> GetReferencedVariables(String input)
|
||||
{
|
||||
Int32 nestedCount = -1;
|
||||
Boolean insideMatch = false;
|
||||
StringBuilder currentMatch = new StringBuilder();
|
||||
HashSet<String> result = new HashSet<String>();
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (!insideMatch && input[i] == '$' && i + 1 < input.Length && input[i + 1] == '(')
|
||||
{
|
||||
insideMatch = true;
|
||||
}
|
||||
|
||||
if (insideMatch)
|
||||
{
|
||||
currentMatch.Append(input[i]);
|
||||
}
|
||||
|
||||
if (insideMatch && input[i] == '(')
|
||||
{
|
||||
nestedCount++;
|
||||
}
|
||||
|
||||
if (insideMatch && input[i] == ')')
|
||||
{
|
||||
if (nestedCount == 0)
|
||||
{
|
||||
result.Add(currentMatch.ToString());
|
||||
currentMatch.Clear();
|
||||
insideMatch = false;
|
||||
nestedCount = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nestedCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (insideMatch || nestedCount >= 0)
|
||||
{
|
||||
// We didn't finish the last match, that means it isn't correct so we will ignore it
|
||||
Debug.Fail("We didn't finish the last match!!!!!");
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
private static readonly Lazy<Regex> s_variableReferenceRegex = new Lazy<Regex>(() => new Regex(@"\$\(([^)]+)\)", RegexOptions.Singleline | RegexOptions.Compiled), true);
|
||||
private static readonly Lazy<Regex> s_conditionVariableReferenceRegex = new Lazy<Regex>(() => new Regex(@"variables\['([^']+)\']", RegexOptions.Singleline | RegexOptions.Compiled), true);
|
||||
private const String c_conditionReplacementFormat = "'{0}'";
|
||||
private const String c_variableFormat = "$({0})";
|
||||
private const String c_conditionVariableFormat = "variables['{0}']";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user