mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
delete un-used code.
This commit is contained in:
committed by
TingluoHuang
parent
f78d35dc4e
commit
f2db563c89
@@ -166,7 +166,6 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
public static readonly string AAD = "AAD";
|
public static readonly string AAD = "AAD";
|
||||||
public static readonly string OAuthAccessToken = "OAuthAccessToken";
|
public static readonly string OAuthAccessToken = "OAuthAccessToken";
|
||||||
public static readonly string PAT = "PAT";
|
|
||||||
public static readonly string OAuth = "OAuth";
|
public static readonly string OAuth = "OAuth";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
|
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
|
||||||
{ Constants.Configuration.PAT, typeof(PersonalAccessToken)},
|
|
||||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -190,42 +190,4 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
|
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PersonalAccessToken : CredentialProvider
|
|
||||||
{
|
|
||||||
public PersonalAccessToken() : base(Constants.Configuration.PAT) { }
|
|
||||||
|
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(context, nameof(context));
|
|
||||||
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
|
|
||||||
trace.Info(nameof(GetVssCredentials));
|
|
||||||
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
|
|
||||||
string token;
|
|
||||||
if (!CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out token))
|
|
||||||
{
|
|
||||||
token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(token, nameof(token));
|
|
||||||
|
|
||||||
trace.Info("token retrieved: {0} chars", token.Length);
|
|
||||||
|
|
||||||
// PAT uses a basic credential
|
|
||||||
VssBasicCredential basicCred = new VssBasicCredential("ActionsRunner", token);
|
|
||||||
VssCredentials creds = new VssCredentials(null, basicCred, CredentialPromptType.DoNotPrompt);
|
|
||||||
trace.Info("cred created");
|
|
||||||
|
|
||||||
return creds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(context, nameof(context));
|
|
||||||
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
|
|
||||||
trace.Info(nameof(EnsureCredential));
|
|
||||||
ArgUtil.NotNull(command, nameof(command));
|
|
||||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ namespace GitHub.Runner.Listener
|
|||||||
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
||||||
bool Cancel(JobCancelMessage message);
|
bool Cancel(JobCancelMessage message);
|
||||||
Task WaitAsync(CancellationToken token);
|
Task WaitAsync(CancellationToken token);
|
||||||
TaskResult GetLocalRunJobResult(AgentJobRequestMessage message);
|
|
||||||
Task ShutdownAsync();
|
Task ShutdownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,11 +164,6 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskResult GetLocalRunJobResult(AgentJobRequestMessage message)
|
|
||||||
{
|
|
||||||
return _localRunJobResult.Value[message.RequestId];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ShutdownAsync()
|
public async Task ShutdownAsync()
|
||||||
{
|
{
|
||||||
Trace.Info($"Shutting down JobDispatcher. Make sure all WorkerDispatcher has finished.");
|
Trace.Info($"Shutting down JobDispatcher. Make sure all WorkerDispatcher has finished.");
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ namespace GitHub.Runner.Worker
|
|||||||
TaskResult? CommandResult { get; set; }
|
TaskResult? CommandResult { get; set; }
|
||||||
CancellationToken CancellationToken { get; }
|
CancellationToken CancellationToken { get; }
|
||||||
List<ServiceEndpoint> Endpoints { get; }
|
List<ServiceEndpoint> Endpoints { get; }
|
||||||
List<SecureFile> SecureFiles { get; }
|
|
||||||
|
|
||||||
PlanFeatures Features { get; }
|
PlanFeatures Features { get; }
|
||||||
Variables Variables { get; }
|
Variables Variables { get; }
|
||||||
@@ -136,7 +135,6 @@ namespace GitHub.Runner.Worker
|
|||||||
public Task ForceCompleted => _forceCompleted.Task;
|
public Task ForceCompleted => _forceCompleted.Task;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||||
public List<SecureFile> SecureFiles { get; private set; }
|
|
||||||
public Variables Variables { get; private set; }
|
public Variables Variables { get; private set; }
|
||||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||||
public HashSet<string> OutputVariables => _outputvariables;
|
public HashSet<string> OutputVariables => _outputvariables;
|
||||||
@@ -257,7 +255,6 @@ namespace GitHub.Runner.Worker
|
|||||||
child.Features = Features;
|
child.Features = Features;
|
||||||
child.Variables = Variables;
|
child.Variables = Variables;
|
||||||
child.Endpoints = Endpoints;
|
child.Endpoints = Endpoints;
|
||||||
child.SecureFiles = SecureFiles;
|
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -549,9 +546,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// Endpoints
|
// Endpoints
|
||||||
Endpoints = message.Resources.Endpoints;
|
Endpoints = message.Resources.Endpoints;
|
||||||
|
|
||||||
// SecureFiles
|
|
||||||
SecureFiles = message.Resources.SecureFiles;
|
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
Variables = new Variables(HostContext, message.Variables);
|
Variables = new Variables(HostContext, message.Variables);
|
||||||
|
|
||||||
|
|||||||
@@ -178,15 +178,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add masks for secure file download tickets
|
|
||||||
foreach (SecureFile file in message.Resources.SecureFiles ?? new List<SecureFile>())
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(file.Ticket))
|
|
||||||
{
|
|
||||||
HostContext.SecretMasker.AddValue(file.Ticket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetCulture(Pipelines.AgentJobRequestMessage message)
|
private void SetCulture(Pipelines.AgentJobRequestMessage message)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class AndNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
foreach (ExpressionNode parameter in Parameters)
|
|
||||||
{
|
|
||||||
if (!parameter.EvaluateBoolean(context))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class CoalesceNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
EvaluationResult result = null;
|
|
||||||
foreach (ExpressionNode parameter in Parameters)
|
|
||||||
{
|
|
||||||
result = parameter.Evaluate(context);
|
|
||||||
if (result.Kind == ValueKind.Null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Kind == ValueKind.String && String.IsNullOrEmpty(result.Value as String))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result?.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class JArrayAccessor : IReadOnlyArray
|
|
||||||
{
|
|
||||||
public JArrayAccessor(JArray jarray)
|
|
||||||
{
|
|
||||||
m_jarray = jarray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_jarray.Count;
|
|
||||||
|
|
||||||
public Object this[Int32 index] => m_jarray[index];
|
|
||||||
|
|
||||||
public IEnumerator<Object> GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_jarray.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_jarray.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly JArray m_jarray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class JObjectAccessor : IReadOnlyObject
|
|
||||||
{
|
|
||||||
public JObjectAccessor(JObject jobject)
|
|
||||||
{
|
|
||||||
m_jobject = jobject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_jobject.Count;
|
|
||||||
|
|
||||||
public IEnumerable<String> Keys => (m_jobject as IDictionary<String, JToken>).Keys;
|
|
||||||
|
|
||||||
// This uses Select. Calling .Values directly throws an exception.
|
|
||||||
public IEnumerable<Object> Values => (m_jobject as IDictionary<String, JToken>).Select(x => x.Value);
|
|
||||||
|
|
||||||
public Object this[String key] => m_jobject[key];
|
|
||||||
|
|
||||||
public Boolean ContainsKey(String key)
|
|
||||||
{
|
|
||||||
return (m_jobject as IDictionary<String, JToken>).ContainsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryGetValue(
|
|
||||||
String key,
|
|
||||||
out Object value)
|
|
||||||
{
|
|
||||||
if ((m_jobject as IDictionary<String, JToken>).TryGetValue(key, out JToken val))
|
|
||||||
{
|
|
||||||
value = val;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly JObject m_jobject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class JsonDictionaryContractAccessor : IReadOnlyObject
|
|
||||||
{
|
|
||||||
public JsonDictionaryContractAccessor(
|
|
||||||
JsonDictionaryContract contract,
|
|
||||||
Object obj)
|
|
||||||
{
|
|
||||||
m_contract = contract;
|
|
||||||
m_obj = obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var genericMethod = s_getCountTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
|
|
||||||
return (Int32)genericMethod.Invoke(null, new[] { m_obj });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<String> Keys
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var genericMethod = s_getKeysTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
|
|
||||||
return genericMethod.Invoke(null, new[] { m_obj }) as IEnumerable<String>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Object> Values => Keys.Select(x => this[x]);
|
|
||||||
|
|
||||||
public Object this[String key]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (TryGetValue(key, out Object value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean ContainsKey(String key)
|
|
||||||
{
|
|
||||||
return TryGetValue(key, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryGetValue(
|
|
||||||
String key,
|
|
||||||
out Object value)
|
|
||||||
{
|
|
||||||
var genericMethod = s_tryGetValueTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
|
|
||||||
var tuple = genericMethod.Invoke(null, new[] { m_obj, key }) as Tuple<Boolean, Object>;
|
|
||||||
value = tuple.Item2;
|
|
||||||
return tuple.Item1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Int32 GetCount<TValue>(IDictionary<String, TValue> dictionary)
|
|
||||||
{
|
|
||||||
return dictionary.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<String> GetKeys<TValue>(IDictionary<String, TValue> dictionary)
|
|
||||||
{
|
|
||||||
return dictionary.Keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Tuple<Boolean, Object> TryGetValue<TValue>(
|
|
||||||
IDictionary<String, TValue> dictionary,
|
|
||||||
String key)
|
|
||||||
{
|
|
||||||
if (dictionary.TryGetValue(key, out TValue value))
|
|
||||||
{
|
|
||||||
return new Tuple<Boolean, Object>(true, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<Boolean, Object>(false, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Lazy<MethodInfo> s_getCountTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetCount), BindingFlags.NonPublic | BindingFlags.Static));
|
|
||||||
private static Lazy<MethodInfo> s_getKeysTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetKeys), BindingFlags.NonPublic | BindingFlags.Static));
|
|
||||||
private static Lazy<MethodInfo> s_tryGetValueTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static));
|
|
||||||
private readonly JsonDictionaryContract m_contract;
|
|
||||||
private readonly Object m_obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class JsonObjectContractAccessor : IReadOnlyObject
|
|
||||||
{
|
|
||||||
public JsonObjectContractAccessor(
|
|
||||||
JsonObjectContract contract,
|
|
||||||
Object obj)
|
|
||||||
{
|
|
||||||
m_contract = contract;
|
|
||||||
m_obj = obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => GetProperties().Count();
|
|
||||||
|
|
||||||
public IEnumerable<String> Keys => GetProperties().Select(x => x.PropertyName);
|
|
||||||
|
|
||||||
public IEnumerable<Object> Values => GetProperties().Select(x => x.ValueProvider.GetValue(m_obj));
|
|
||||||
|
|
||||||
public Object this[String key]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (TryGetValue(key, out Object value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean ContainsKey(String key)
|
|
||||||
{
|
|
||||||
return TryGetProperty(key, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryGetValue(
|
|
||||||
String key,
|
|
||||||
out Object value)
|
|
||||||
{
|
|
||||||
if (TryGetProperty(key, out JsonProperty property))
|
|
||||||
{
|
|
||||||
value = property.ValueProvider.GetValue(m_obj);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<JsonProperty> GetProperties()
|
|
||||||
{
|
|
||||||
return m_contract.Properties.Where(x => !x.Ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean TryGetProperty(
|
|
||||||
String key,
|
|
||||||
out JsonProperty property)
|
|
||||||
{
|
|
||||||
property = m_contract.Properties.GetClosestMatchProperty(key);
|
|
||||||
if (property != null && !property.Ignored)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
property = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly JsonObjectContract m_contract;
|
|
||||||
private readonly Object m_obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class ListOfObjectAccessor : IReadOnlyArray
|
|
||||||
{
|
|
||||||
public ListOfObjectAccessor(IList<Object> list)
|
|
||||||
{
|
|
||||||
m_list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_list.Count;
|
|
||||||
|
|
||||||
public Object this[Int32 index] => m_list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Object> GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_list.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_list.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IList<Object> m_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class ReadOnlyDictionaryOfStringObjectAccessor : IReadOnlyObject
|
|
||||||
{
|
|
||||||
public ReadOnlyDictionaryOfStringObjectAccessor(IReadOnlyDictionary<String, Object> dictionary)
|
|
||||||
{
|
|
||||||
m_dictionary = dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_dictionary.Count;
|
|
||||||
|
|
||||||
public IEnumerable<String> Keys => m_dictionary.Keys;
|
|
||||||
|
|
||||||
public IEnumerable<Object> Values => m_dictionary.Values;
|
|
||||||
|
|
||||||
public Object this[String key] => m_dictionary[key];
|
|
||||||
|
|
||||||
public Boolean ContainsKey(String key) => m_dictionary.ContainsKey(key);
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator() => m_dictionary.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => m_dictionary.GetEnumerator();
|
|
||||||
|
|
||||||
public Boolean TryGetValue(
|
|
||||||
String key,
|
|
||||||
out Object value)
|
|
||||||
{
|
|
||||||
return m_dictionary.TryGetValue(key, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IReadOnlyDictionary<String, Object> m_dictionary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class ReadOnlyDictionaryOfStringStringAccessor : IReadOnlyObject
|
|
||||||
{
|
|
||||||
public ReadOnlyDictionaryOfStringStringAccessor(IReadOnlyDictionary<String, String> dictionary)
|
|
||||||
{
|
|
||||||
m_dictionary = dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_dictionary.Count;
|
|
||||||
|
|
||||||
public IEnumerable<String> Keys => m_dictionary.Keys;
|
|
||||||
|
|
||||||
public IEnumerable<Object> Values => m_dictionary.Values.OfType<Object>();
|
|
||||||
|
|
||||||
public Object this[String key] => m_dictionary[key];
|
|
||||||
|
|
||||||
public Boolean ContainsKey(String key)
|
|
||||||
{
|
|
||||||
return m_dictionary.ContainsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryGetValue(
|
|
||||||
String key,
|
|
||||||
out Object value)
|
|
||||||
{
|
|
||||||
if (m_dictionary.TryGetValue(key, out String val))
|
|
||||||
{
|
|
||||||
value = val;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IReadOnlyDictionary<String, String> m_dictionary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
|
|
||||||
{
|
|
||||||
internal sealed class ReadOnlyListOfObjectAccessor : IReadOnlyArray
|
|
||||||
{
|
|
||||||
public ReadOnlyListOfObjectAccessor(IReadOnlyList<Object> list)
|
|
||||||
{
|
|
||||||
m_list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_list.Count;
|
|
||||||
|
|
||||||
public Object this[Int32 index] => m_list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Object> GetEnumerator() => m_list.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => m_list.GetEnumerator();
|
|
||||||
|
|
||||||
private readonly IReadOnlyList<Object> m_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public abstract class ContainerNode : ExpressionNode
|
|
||||||
{
|
|
||||||
public IReadOnlyList<ExpressionNode> Parameters => m_parameters.AsReadOnly();
|
|
||||||
|
|
||||||
public void AddParameter(ExpressionNode node)
|
|
||||||
{
|
|
||||||
m_parameters.Add(node);
|
|
||||||
node.Container = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReplaceParameter(Int32 index, ExpressionNode node)
|
|
||||||
{
|
|
||||||
m_parameters[index] = node;
|
|
||||||
node.Container = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IEnumerable<T> GetParameters<T>()
|
|
||||||
{
|
|
||||||
List<T> matched = new List<T>();
|
|
||||||
Queue<IExpressionNode> parameters = new Queue<IExpressionNode>(this.Parameters);
|
|
||||||
|
|
||||||
while (parameters.Count > 0)
|
|
||||||
{
|
|
||||||
var parameter = parameters.Dequeue();
|
|
||||||
if (typeof(T).GetTypeInfo().IsAssignableFrom(parameter.GetType().GetTypeInfo()))
|
|
||||||
{
|
|
||||||
matched.Add((T)parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var childParameter in parameter.GetParameters<T>())
|
|
||||||
{
|
|
||||||
parameters.Enqueue(childParameter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matched;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<ExpressionNode> m_parameters = new List<ExpressionNode>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class ContainsNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
String left = Parameters[0].EvaluateString(context) as String ?? String.Empty;
|
|
||||||
String right = Parameters[1].EvaluateString(context) as String ?? String.Empty;
|
|
||||||
return left.IndexOf(right, StringComparison.OrdinalIgnoreCase) >= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class ContainsValueNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
EvaluationResult left = Parameters[0].Evaluate(context);
|
|
||||||
|
|
||||||
if (left.TryGetCollectionInterface(out Object collection))
|
|
||||||
{
|
|
||||||
EvaluationResult right = Parameters[1].Evaluate(context);
|
|
||||||
|
|
||||||
if (collection is IReadOnlyArray array)
|
|
||||||
{
|
|
||||||
foreach (var item in array)
|
|
||||||
{
|
|
||||||
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
|
|
||||||
|
|
||||||
if (right.Equals(context, itemResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (collection is IReadOnlyObject obj)
|
|
||||||
{
|
|
||||||
foreach (var value in obj.Values)
|
|
||||||
{
|
|
||||||
var valueResult = EvaluationResult.CreateIntermediateResult(context, value, out _);
|
|
||||||
|
|
||||||
if (right.Equals(context, valueResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public struct ConversionResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Result object after the conversion
|
|
||||||
/// </summary>
|
|
||||||
public Object Result;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Memory overhead for the result object
|
|
||||||
/// </summary>
|
|
||||||
public ResultMemory ResultMemory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class EndsWithNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
String left = Parameters[0].EvaluateString(context) ?? String.Empty;
|
|
||||||
String right = Parameters[1].EvaluateString(context) ?? String.Empty;
|
|
||||||
return left.EndsWith(right, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class EqualNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].Evaluate(context).Equals(context, Parameters[1].Evaluate(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class EvaluationContext
|
|
||||||
{
|
|
||||||
internal EvaluationContext(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options,
|
|
||||||
ExpressionNode node)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(trace, nameof(trace));
|
|
||||||
ArgumentUtility.CheckForNull(secretMasker, nameof(secretMasker));
|
|
||||||
Trace = trace;
|
|
||||||
SecretMasker = secretMasker;
|
|
||||||
State = state;
|
|
||||||
|
|
||||||
// Copy the options
|
|
||||||
options = new EvaluationOptions(copy: options);
|
|
||||||
if (options.MaxMemory == 0)
|
|
||||||
{
|
|
||||||
// Set a reasonable default max memory
|
|
||||||
options.MaxMemory = 1048576; // 1 mb
|
|
||||||
}
|
|
||||||
Options = options;
|
|
||||||
Memory = new EvaluationMemory(options.MaxMemory, node);
|
|
||||||
|
|
||||||
m_traceResults = new Dictionary<ExpressionNode, String>();
|
|
||||||
m_traceMemory = new MemoryCounter(null, options.MaxMemory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITraceWriter Trace { get; }
|
|
||||||
|
|
||||||
public ISecretMasker SecretMasker { get; }
|
|
||||||
|
|
||||||
public Object State { get; }
|
|
||||||
|
|
||||||
internal EvaluationMemory Memory { get; }
|
|
||||||
|
|
||||||
internal EvaluationOptions Options { get; }
|
|
||||||
|
|
||||||
internal void SetTraceResult(
|
|
||||||
ExpressionNode node,
|
|
||||||
EvaluationResult result)
|
|
||||||
{
|
|
||||||
// Remove if previously added. This typically should not happen. This could happen
|
|
||||||
// due to a badly authored function. So we'll handle it and track memory correctly.
|
|
||||||
if (m_traceResults.TryGetValue(node, out String oldValue))
|
|
||||||
{
|
|
||||||
m_traceMemory.Remove(oldValue);
|
|
||||||
m_traceResults.Remove(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check max memory
|
|
||||||
String value = ExpressionUtil.FormatValue(SecretMasker, result);
|
|
||||||
if (m_traceMemory.TryAdd(value))
|
|
||||||
{
|
|
||||||
// Store the result
|
|
||||||
m_traceResults[node] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Boolean TryGetTraceResult(ExpressionNode node, out String value)
|
|
||||||
{
|
|
||||||
return m_traceResults.TryGetValue(node, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<ExpressionNode, String> m_traceResults = new Dictionary<ExpressionNode, String>();
|
|
||||||
private readonly MemoryCounter m_traceMemory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This is an internal class only.
|
|
||||||
///
|
|
||||||
/// This class is used to track current memory consumption
|
|
||||||
/// across the entire expression evaluation.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class EvaluationMemory
|
|
||||||
{
|
|
||||||
internal EvaluationMemory(
|
|
||||||
Int32 maxBytes,
|
|
||||||
ExpressionNode node)
|
|
||||||
{
|
|
||||||
m_maxAmount = maxBytes;
|
|
||||||
m_node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void AddAmount(
|
|
||||||
Int32 depth,
|
|
||||||
Int32 bytes,
|
|
||||||
Boolean trimDepth = false)
|
|
||||||
{
|
|
||||||
// Trim deeper depths
|
|
||||||
if (trimDepth)
|
|
||||||
{
|
|
||||||
while (m_maxActiveDepth > depth)
|
|
||||||
{
|
|
||||||
var amount = m_depths[m_maxActiveDepth];
|
|
||||||
|
|
||||||
if (amount > 0)
|
|
||||||
{
|
|
||||||
// Sanity check
|
|
||||||
if (amount > m_totalAmount)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Bytes to subtract exceeds total bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract from the total
|
|
||||||
checked
|
|
||||||
{
|
|
||||||
m_totalAmount -= amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the amount
|
|
||||||
m_depths[m_maxActiveDepth] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_maxActiveDepth--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grow the depths
|
|
||||||
if (depth > m_maxActiveDepth)
|
|
||||||
{
|
|
||||||
// Grow the list
|
|
||||||
while (m_depths.Count <= depth)
|
|
||||||
{
|
|
||||||
m_depths.Add(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the max active depth
|
|
||||||
m_maxActiveDepth = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
checked
|
|
||||||
{
|
|
||||||
// Add to the depth
|
|
||||||
m_depths[depth] += bytes;
|
|
||||||
|
|
||||||
// Add to the total
|
|
||||||
m_totalAmount += bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check max
|
|
||||||
if (m_totalAmount > m_maxAmount)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(ExpressionResources.ExceededAllowedMemory(m_node?.ConvertToExpression()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Int32 CalculateBytes(Object obj)
|
|
||||||
{
|
|
||||||
if (obj is String str)
|
|
||||||
{
|
|
||||||
// This measurement doesn't have to be perfect
|
|
||||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
|
||||||
|
|
||||||
checked
|
|
||||||
{
|
|
||||||
return c_stringBaseOverhead + ((str?.Length ?? 0) * sizeof(Char));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return c_minObjectSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const Int32 c_minObjectSize = 24;
|
|
||||||
private const Int32 c_stringBaseOverhead = 26;
|
|
||||||
private readonly List<Int32> m_depths = new List<Int32>();
|
|
||||||
private readonly Int32 m_maxAmount;
|
|
||||||
private readonly ExpressionNode m_node;
|
|
||||||
private Int32 m_maxActiveDepth = -1;
|
|
||||||
private Int32 m_totalAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class EvaluationOptions
|
|
||||||
{
|
|
||||||
public EvaluationOptions()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public EvaluationOptions(EvaluationOptions copy)
|
|
||||||
{
|
|
||||||
if (copy != null)
|
|
||||||
{
|
|
||||||
Converters = copy.Converters;
|
|
||||||
MaxMemory = copy.MaxMemory;
|
|
||||||
TimeZone = copy.TimeZone;
|
|
||||||
UseCollectionInterfaces = copy.UseCollectionInterfaces;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converters allow types to be coerced into data that is friendly
|
|
||||||
/// for expression functions to operate on it.
|
|
||||||
///
|
|
||||||
/// As each node in the expression tree is evaluated, converters are applied.
|
|
||||||
/// When a node's result matches a converter type, the result is intercepted
|
|
||||||
/// by the converter, and converter result is used instead.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public IDictionary<Type, Converter<Object, ConversionResult>> Converters { get; set; }
|
|
||||||
|
|
||||||
public Int32 MaxMemory { get; set; }
|
|
||||||
|
|
||||||
public TimeZoneInfo TimeZone { get; set; }
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public Boolean UseCollectionInterfaces { get; set; } // Feature flag for now behavior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,828 +0,0 @@
|
|||||||
using GitHub.DistributedTask.Expressions.CollectionAccessors;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class EvaluationResult
|
|
||||||
{
|
|
||||||
internal EvaluationResult(
|
|
||||||
EvaluationContext context,
|
|
||||||
Int32 level,
|
|
||||||
Object val,
|
|
||||||
ValueKind kind,
|
|
||||||
Object raw)
|
|
||||||
: this(context, level, val, kind, raw, false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal EvaluationResult(
|
|
||||||
EvaluationContext context,
|
|
||||||
Int32 level,
|
|
||||||
Object val,
|
|
||||||
ValueKind kind,
|
|
||||||
Object raw,
|
|
||||||
Boolean omitTracing)
|
|
||||||
{
|
|
||||||
m_level = level;
|
|
||||||
Value = val;
|
|
||||||
Kind = kind;
|
|
||||||
Raw = raw;
|
|
||||||
m_omitTracing = omitTracing;
|
|
||||||
|
|
||||||
if (!omitTracing)
|
|
||||||
{
|
|
||||||
TraceValue(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueKind Kind { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When a custom converter is applied to the node result, raw contains the original value
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public Object Raw { get; }
|
|
||||||
|
|
||||||
public Object Value { get; }
|
|
||||||
|
|
||||||
public int CompareTo(
|
|
||||||
EvaluationContext context,
|
|
||||||
EvaluationResult right)
|
|
||||||
{
|
|
||||||
Object leftValue;
|
|
||||||
ValueKind leftKind;
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
case ValueKind.Number:
|
|
||||||
case ValueKind.String:
|
|
||||||
case ValueKind.Version:
|
|
||||||
leftValue = Value;
|
|
||||||
leftKind = Kind;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
leftValue = ConvertToNumber(context); // Will throw or succeed
|
|
||||||
leftKind = ValueKind.Number;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftKind == ValueKind.Boolean)
|
|
||||||
{
|
|
||||||
Boolean b = right.ConvertToBoolean(context);
|
|
||||||
return ((Boolean)leftValue).CompareTo(b);
|
|
||||||
}
|
|
||||||
else if (leftKind == ValueKind.DateTime)
|
|
||||||
{
|
|
||||||
DateTimeOffset d = right.ConvertToDateTime(context);
|
|
||||||
return ((DateTimeOffset)leftValue).CompareTo(d);
|
|
||||||
}
|
|
||||||
else if (leftKind == ValueKind.Number)
|
|
||||||
{
|
|
||||||
Decimal d = right.ConvertToNumber(context);
|
|
||||||
return ((Decimal)leftValue).CompareTo(d);
|
|
||||||
}
|
|
||||||
else if (leftKind == ValueKind.String)
|
|
||||||
{
|
|
||||||
String s = right.ConvertToString(context);
|
|
||||||
return String.Compare(leftValue as String ?? String.Empty, s ?? String.Empty, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
else //if (leftKind == ValueKind.Version)
|
|
||||||
{
|
|
||||||
Version v = right.ConvertToVersion(context);
|
|
||||||
return (leftValue as Version).CompareTo(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean ConvertToBoolean(EvaluationContext context)
|
|
||||||
{
|
|
||||||
Boolean result;
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
return (Boolean)Value; // Not converted. Don't trace.
|
|
||||||
|
|
||||||
case ValueKind.Number:
|
|
||||||
result = (Decimal)Value != 0m; // 0 converts to false, otherwise true.
|
|
||||||
TraceValue(context, result, ValueKind.Boolean);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
result = !String.IsNullOrEmpty(Value as String);
|
|
||||||
TraceValue(context, result, ValueKind.Boolean);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
case ValueKind.Object:
|
|
||||||
case ValueKind.Version:
|
|
||||||
result = true;
|
|
||||||
TraceValue(context, result, ValueKind.Boolean);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
case ValueKind.Null:
|
|
||||||
result = false;
|
|
||||||
TraceValue(context, result, ValueKind.Boolean);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to convert value to Boolean. Unexpected value kind '{Kind}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset ConvertToDateTime(EvaluationContext context)
|
|
||||||
{
|
|
||||||
DateTimeOffset result;
|
|
||||||
if (TryConvertToDateTime(context, out result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.DateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object ConvertToNull(EvaluationContext context)
|
|
||||||
{
|
|
||||||
Object result;
|
|
||||||
if (TryConvertToNull(context, out result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Decimal ConvertToNumber(EvaluationContext context)
|
|
||||||
{
|
|
||||||
Decimal result;
|
|
||||||
if (TryConvertToNumber(context, out result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Number);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String ConvertToString(EvaluationContext context)
|
|
||||||
{
|
|
||||||
String result;
|
|
||||||
if (TryConvertToString(context, out result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version ConvertToVersion(EvaluationContext context)
|
|
||||||
{
|
|
||||||
Version result;
|
|
||||||
if (TryConvertToVersion(context, out result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean Equals(
|
|
||||||
EvaluationContext context,
|
|
||||||
EvaluationResult right)
|
|
||||||
{
|
|
||||||
if (Kind == ValueKind.Boolean)
|
|
||||||
{
|
|
||||||
Boolean b = right.ConvertToBoolean(context);
|
|
||||||
return (Boolean)Value == b;
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.DateTime)
|
|
||||||
{
|
|
||||||
DateTimeOffset d;
|
|
||||||
if (right.TryConvertToDateTime(context, out d))
|
|
||||||
{
|
|
||||||
return (DateTimeOffset)Value == d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.Number)
|
|
||||||
{
|
|
||||||
Decimal d;
|
|
||||||
if (right.TryConvertToNumber(context, out d))
|
|
||||||
{
|
|
||||||
return (Decimal)Value == d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.Version)
|
|
||||||
{
|
|
||||||
Version v;
|
|
||||||
if (right.TryConvertToVersion(context, out v))
|
|
||||||
{
|
|
||||||
return (Version)Value == v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.String)
|
|
||||||
{
|
|
||||||
String s;
|
|
||||||
if (right.TryConvertToString(context, out s))
|
|
||||||
{
|
|
||||||
return String.Equals(
|
|
||||||
Value as String ?? String.Empty,
|
|
||||||
s ?? String.Empty,
|
|
||||||
StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.Array || Kind == ValueKind.Object)
|
|
||||||
{
|
|
||||||
return Kind == right.Kind && Object.ReferenceEquals(Value, right.Value);
|
|
||||||
}
|
|
||||||
else if (Kind == ValueKind.Null)
|
|
||||||
{
|
|
||||||
Object n;
|
|
||||||
if (right.TryConvertToNull(context, out n))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryConvertToDateTime(
|
|
||||||
EvaluationContext context,
|
|
||||||
out DateTimeOffset result)
|
|
||||||
{
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
result = (DateTimeOffset)Value; // Not converted. Don't trace again.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
if (TryParseDateTime(context?.Options, Value as String, out result))
|
|
||||||
{
|
|
||||||
TraceValue(context, result, ValueKind.DateTime);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.DateTime);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
case ValueKind.Null:
|
|
||||||
case ValueKind.Number:
|
|
||||||
case ValueKind.Object:
|
|
||||||
case ValueKind.Version:
|
|
||||||
result = default;
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.DateTime);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to determine whether value can be converted to Number. Unexpected value kind '{Kind}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryConvertToNull(
|
|
||||||
EvaluationContext context,
|
|
||||||
out Object result)
|
|
||||||
{
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Null:
|
|
||||||
result = null; // Not converted. Don't trace again.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
if (String.IsNullOrEmpty(Value as String))
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
TraceValue(context, result, ValueKind.Null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = null;
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Null);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryConvertToNumber(
|
|
||||||
EvaluationContext context,
|
|
||||||
out Decimal result)
|
|
||||||
{
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
result = (Boolean)Value ? 1m : 0m;
|
|
||||||
TraceValue(context, result, ValueKind.Number);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Number:
|
|
||||||
result = (Decimal)Value; // Not converted. Don't trace again.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
String s = Value as String ?? String.Empty;
|
|
||||||
if (String.IsNullOrEmpty(s))
|
|
||||||
{
|
|
||||||
result = 0m;
|
|
||||||
TraceValue(context, result, ValueKind.Number);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Decimal.TryParse(s, s_numberStyles, CultureInfo.InvariantCulture, out result))
|
|
||||||
{
|
|
||||||
TraceValue(context, result, ValueKind.Number);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Number);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
case ValueKind.Object:
|
|
||||||
case ValueKind.Version:
|
|
||||||
result = default(Decimal);
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Number);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.Null:
|
|
||||||
result = 0m;
|
|
||||||
TraceValue(context, result, ValueKind.Number);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to determine whether value can be converted to Number. Unexpected value kind '{Kind}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryConvertToString(
|
|
||||||
EvaluationContext context,
|
|
||||||
out String result)
|
|
||||||
{
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
result = String.Format(CultureInfo.InvariantCulture, "{0}", Value);
|
|
||||||
TraceValue(context, result, ValueKind.String);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
result = ((DateTimeOffset)Value).ToString(ExpressionConstants.DateTimeFormat, CultureInfo.InvariantCulture);
|
|
||||||
TraceValue(context, result, ValueKind.String);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Number:
|
|
||||||
result = ((Decimal)Value).ToString(ExpressionConstants.NumberFormat, CultureInfo.InvariantCulture);
|
|
||||||
TraceValue(context, result, ValueKind.String);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
result = Value as String; // Not converted. Don't trace.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Version:
|
|
||||||
result = (Value as Version).ToString();
|
|
||||||
TraceValue(context, result, ValueKind.String);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Null:
|
|
||||||
result = String.Empty;
|
|
||||||
TraceValue(context, result, ValueKind.Null);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.Object:
|
|
||||||
result = null;
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.String);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to convert to String. Unexpected value kind '{Kind}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryConvertToVersion(
|
|
||||||
EvaluationContext context,
|
|
||||||
out Version result)
|
|
||||||
{
|
|
||||||
switch (Kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
result = null;
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Version);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.Number:
|
|
||||||
if (Version.TryParse(ConvertToString(context), out result))
|
|
||||||
{
|
|
||||||
TraceValue(context, result, ValueKind.Version);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Version);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
String s = Value as String ?? String.Empty;
|
|
||||||
if (Version.TryParse(s, out result))
|
|
||||||
{
|
|
||||||
TraceValue(context, result, ValueKind.Version);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Version);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case ValueKind.Version:
|
|
||||||
result = Value as Version; // Not converted. Don't trace again.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
case ValueKind.Object:
|
|
||||||
case ValueKind.Null:
|
|
||||||
result = null;
|
|
||||||
TraceCoercionFailed(context, toKind: ValueKind.Version);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to convert to Version. Unexpected value kind '{Kind}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public Boolean TryGetCollectionInterface(out Object collection)
|
|
||||||
{
|
|
||||||
if ((Kind == ValueKind.Object || Kind == ValueKind.Array))
|
|
||||||
{
|
|
||||||
var obj = Value;
|
|
||||||
if (obj is IReadOnlyObject)
|
|
||||||
{
|
|
||||||
collection = obj;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IDictionary<String, String> dictionary1)
|
|
||||||
{
|
|
||||||
collection = new ReadOnlyDictionaryOfStringStringAccessor(new ReadOnlyDictionary<String, String>(dictionary1));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IDictionary<String, Object> dictionary2)
|
|
||||||
{
|
|
||||||
collection = new ReadOnlyDictionaryOfStringObjectAccessor(new ReadOnlyDictionary<String, Object>(dictionary2));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IReadOnlyDictionary<String, String> dictionary3)
|
|
||||||
{
|
|
||||||
collection = new ReadOnlyDictionaryOfStringStringAccessor(dictionary3);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IReadOnlyDictionary<String, Object> dictionary4)
|
|
||||||
{
|
|
||||||
collection = new ReadOnlyDictionaryOfStringObjectAccessor(dictionary4);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is JObject jobject)
|
|
||||||
{
|
|
||||||
collection = new JObjectAccessor(jobject);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IReadOnlyArray)
|
|
||||||
{
|
|
||||||
collection = obj;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IList<Object> list1)
|
|
||||||
{
|
|
||||||
collection = new ListOfObjectAccessor(list1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is IReadOnlyList<Object> list2)
|
|
||||||
{
|
|
||||||
collection = new ReadOnlyListOfObjectAccessor(list2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj is JArray jarray)
|
|
||||||
{
|
|
||||||
collection = new JArrayAccessor(jarray);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contract = s_serializer.Value.ContractResolver.ResolveContract(obj.GetType());
|
|
||||||
if (contract is JsonObjectContract objectContract)
|
|
||||||
{
|
|
||||||
collection = new JsonObjectContractAccessor(objectContract, obj);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (contract is JsonDictionaryContract dictionaryContract && dictionaryContract.DictionaryKeyType == typeof(String))
|
|
||||||
{
|
|
||||||
collection = new JsonDictionaryContractAccessor(dictionaryContract, obj);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collection = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Useful for working with values that are not the direct evaluation result of a parameter.
|
|
||||||
/// This allows ExpressionNode authors to leverage the coercion and comparision functions
|
|
||||||
/// for any values.
|
|
||||||
///
|
|
||||||
/// Also note, the value will be canonicalized (for example numeric types converted to decimal) and any
|
|
||||||
/// matching converters applied.
|
|
||||||
/// </summary>
|
|
||||||
public static EvaluationResult CreateIntermediateResult(
|
|
||||||
EvaluationContext context,
|
|
||||||
Object obj,
|
|
||||||
out ResultMemory conversionResultMemory)
|
|
||||||
{
|
|
||||||
var val = ExpressionUtil.ConvertToCanonicalValue(context?.Options, obj, out ValueKind kind, out Object raw, out conversionResultMemory);
|
|
||||||
return new EvaluationResult(context, 0, val, kind, raw, omitTracing: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraceCoercionFailed(
|
|
||||||
EvaluationContext context,
|
|
||||||
ValueKind toKind)
|
|
||||||
{
|
|
||||||
if (!m_omitTracing)
|
|
||||||
{
|
|
||||||
TraceVerbose(context, String.Format(CultureInfo.InvariantCulture, "=> Unable to coerce {0} to {1}.", Kind, toKind));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraceValue(EvaluationContext context)
|
|
||||||
{
|
|
||||||
if (!m_omitTracing)
|
|
||||||
{
|
|
||||||
TraceValue(context, Value, Kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraceValue(
|
|
||||||
EvaluationContext context,
|
|
||||||
Object val,
|
|
||||||
ValueKind kind)
|
|
||||||
{
|
|
||||||
if (!m_omitTracing)
|
|
||||||
{
|
|
||||||
TraceVerbose(context, String.Concat("=> ", ExpressionUtil.FormatValue(context?.SecretMasker, val, kind)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraceVerbose(
|
|
||||||
EvaluationContext context,
|
|
||||||
String message)
|
|
||||||
{
|
|
||||||
if (!m_omitTracing)
|
|
||||||
{
|
|
||||||
context?.Trace.Verbose(String.Empty.PadLeft(m_level * 2, '.') + (message ?? String.Empty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean TryParseDateTime(
|
|
||||||
EvaluationOptions options,
|
|
||||||
String s,
|
|
||||||
out DateTimeOffset result)
|
|
||||||
{
|
|
||||||
if (String.IsNullOrEmpty(s))
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
s = s.Trim();
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
// Year, month, day, hour, min, sec
|
|
||||||
if (!ReadInt32(s, 4, 4, ref i, out Int32 year) ||
|
|
||||||
!ReadSeparator(s, ref i, new[] { '-', '/' }, out Char dateSeparator) ||
|
|
||||||
!ReadInt32(s, 1, 2, ref i, out Int32 month) ||
|
|
||||||
!ReadSeparator(s, ref i, dateSeparator) ||
|
|
||||||
!ReadInt32(s, 1, 2, ref i, out Int32 day) ||
|
|
||||||
!ReadSeparator(s, ref i, ' ', 'T') ||
|
|
||||||
!ReadInt32(s, 1, 2, ref i, out Int32 hour) ||
|
|
||||||
!ReadSeparator(s, ref i, ':') ||
|
|
||||||
!ReadInt32(s, 1, 2, ref i, out Int32 minute) ||
|
|
||||||
!ReadSeparator(s, ref i, ':') ||
|
|
||||||
!ReadInt32(s, 1, 2, ref i, out Int32 second))
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fraction of second
|
|
||||||
Int32 ticks;
|
|
||||||
if (ExpressionUtil.SafeCharAt(s, i) == '.')
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
if (!ReadDigits(s, 1, 7, ref i, out String digits))
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digits.Length < 7)
|
|
||||||
{
|
|
||||||
digits = digits.PadRight(7, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
ticks = Int32.Parse(digits, NumberStyles.None, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ticks = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan offset;
|
|
||||||
|
|
||||||
// End of string indicates local time zone
|
|
||||||
if (i >= s.Length)
|
|
||||||
{
|
|
||||||
// Determine the offset
|
|
||||||
var timeZone = options?.TimeZone ?? TimeZoneInfo.Local;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var dateTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified);
|
|
||||||
offset = timeZone.GetUtcOffset(dateTime);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Offset, then end of string
|
|
||||||
else if (!ReadOffset(s, ref i, out offset) ||
|
|
||||||
i < s.Length)
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the DateTimeOffset
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = new DateTimeOffset(year, month, day, hour, minute, second, offset);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add fraction of second
|
|
||||||
if (ticks > 0)
|
|
||||||
{
|
|
||||||
result = result.AddTicks(ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean ReadDigits(
|
|
||||||
String str,
|
|
||||||
Int32 minLength,
|
|
||||||
Int32 maxLength,
|
|
||||||
ref Int32 index,
|
|
||||||
out String result)
|
|
||||||
{
|
|
||||||
var startIndex = index;
|
|
||||||
while (Char.IsDigit(ExpressionUtil.SafeCharAt(str, index)))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = index - startIndex;
|
|
||||||
if (length < minLength || length > maxLength)
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = str.Substring(startIndex, length);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean ReadInt32(
|
|
||||||
String str,
|
|
||||||
Int32 minLength,
|
|
||||||
Int32 maxLength,
|
|
||||||
ref Int32 index,
|
|
||||||
out Int32 result)
|
|
||||||
{
|
|
||||||
if (!ReadDigits(str, minLength, maxLength, ref index, out String digits))
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = Int32.Parse(digits, NumberStyles.None, CultureInfo.InvariantCulture);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean ReadSeparator(
|
|
||||||
String str,
|
|
||||||
ref Int32 index,
|
|
||||||
params Char[] allowed)
|
|
||||||
{
|
|
||||||
return ReadSeparator(str, ref index, allowed, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean ReadSeparator(
|
|
||||||
String str,
|
|
||||||
ref Int32 index,
|
|
||||||
Char[] allowed,
|
|
||||||
out Char separator)
|
|
||||||
{
|
|
||||||
separator = ExpressionUtil.SafeCharAt(str, index++);
|
|
||||||
foreach (var a in allowed)
|
|
||||||
{
|
|
||||||
if (separator == a)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
separator = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean ReadOffset(
|
|
||||||
String str,
|
|
||||||
ref Int32 index,
|
|
||||||
out TimeSpan offset)
|
|
||||||
{
|
|
||||||
// Z indicates UTC
|
|
||||||
if (ExpressionUtil.SafeCharAt(str, index) == 'Z')
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
offset = TimeSpan.Zero;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Boolean subtract;
|
|
||||||
|
|
||||||
// Negative
|
|
||||||
if (ExpressionUtil.SafeCharAt(str, index) == '-')
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
subtract = true;
|
|
||||||
}
|
|
||||||
// Positive
|
|
||||||
else if (ExpressionUtil.SafeCharAt(str, index) == '+')
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
subtract = false;
|
|
||||||
}
|
|
||||||
// Invalid
|
|
||||||
else
|
|
||||||
{
|
|
||||||
offset = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hour and minute
|
|
||||||
if (!ReadInt32(str, 1, 2, ref index, out Int32 hour) ||
|
|
||||||
!ReadSeparator(str, ref index, ':') ||
|
|
||||||
!ReadInt32(str, 1, 2, ref index, out Int32 minute))
|
|
||||||
{
|
|
||||||
offset = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the offset
|
|
||||||
if (subtract)
|
|
||||||
{
|
|
||||||
offset = TimeSpan.Zero.Subtract(new TimeSpan(hour, minute, 0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
offset = new TimeSpan(hour, minute, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly NumberStyles s_numberStyles =
|
|
||||||
NumberStyles.AllowDecimalPoint |
|
|
||||||
NumberStyles.AllowLeadingSign |
|
|
||||||
NumberStyles.AllowLeadingWhite |
|
|
||||||
NumberStyles.AllowThousands |
|
|
||||||
NumberStyles.AllowTrailingWhite;
|
|
||||||
private static readonly Lazy<JsonSerializer> s_serializer = new Lazy<JsonSerializer>(() => JsonUtility.CreateJsonSerializer());
|
|
||||||
private readonly Int32 m_level;
|
|
||||||
private readonly Boolean m_omitTracing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class EvaluationTraceWriter : ITraceWriter
|
|
||||||
{
|
|
||||||
public EvaluationTraceWriter(ITraceWriter trace, ISecretMasker secretMasker)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(secretMasker, nameof(secretMasker));
|
|
||||||
m_trace = trace;
|
|
||||||
m_secretMasker = secretMasker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(String message)
|
|
||||||
{
|
|
||||||
if (m_trace != null)
|
|
||||||
{
|
|
||||||
message = m_secretMasker.MaskSecrets(message);
|
|
||||||
m_trace.Info(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(String message)
|
|
||||||
{
|
|
||||||
if (m_trace != null)
|
|
||||||
{
|
|
||||||
message = m_secretMasker.MaskSecrets(message);
|
|
||||||
m_trace.Verbose(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ISecretMasker m_secretMasker;
|
|
||||||
private readonly ITraceWriter m_trace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal static class ExpressionConstants
|
|
||||||
{
|
|
||||||
static ExpressionConstants()
|
|
||||||
{
|
|
||||||
AddFunction<AndNode>("and", 2, Int32.MaxValue);
|
|
||||||
AddFunction<CoalesceNode>("coalesce", 2, Int32.MaxValue);
|
|
||||||
AddFunction<ContainsNode>("contains", 2, 2);
|
|
||||||
AddFunction<ContainsValueNode>("containsValue", 2, 2);
|
|
||||||
AddFunction<EndsWithNode>("endsWith", 2, 2);
|
|
||||||
AddFunction<EqualNode>("eq", 2, 2);
|
|
||||||
AddFunction<FormatNode>("format", 1, Byte.MaxValue);
|
|
||||||
AddFunction<GreaterThanNode>("gt", 2, 2);
|
|
||||||
AddFunction<GreaterThanOrEqualNode>("ge", 2, 2);
|
|
||||||
AddFunction<LessThanNode>("lt", 2, 2);
|
|
||||||
AddFunction<JoinNode>("join", 2, 2);
|
|
||||||
AddFunction<LessThanOrEqualNode>("le", 2, 2);
|
|
||||||
AddFunction<InNode>("in", 2, Int32.MaxValue);
|
|
||||||
AddFunction<NotNode>("not", 1, 1);
|
|
||||||
AddFunction<NotEqualNode>("ne", 2, 2);
|
|
||||||
AddFunction<NotInNode>("notIn", 2, Int32.MaxValue);
|
|
||||||
AddFunction<OrNode>("or", 2, Int32.MaxValue);
|
|
||||||
AddFunction<StartsWithNode>("startsWith", 2, 2);
|
|
||||||
AddFunction<XorNode>("xor", 2, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
|
||||||
where T : FunctionNode, new()
|
|
||||||
{
|
|
||||||
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static readonly String DateTimeFormat = @"yyyy\-MM\-dd\ HH\:mm\:sszzz";
|
|
||||||
internal static readonly Int32 MaxDepth = 50;
|
|
||||||
internal static readonly Int32 MaxLength = 21000; // Under 85,000 large object heap threshold, even if .NET switches to UTF-32
|
|
||||||
internal static readonly String NumberFormat = "0.#######";
|
|
||||||
internal static readonly Dictionary<String, IFunctionInfo> WellKnownFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// Punctuation
|
|
||||||
internal const Char StartIndex = '[';
|
|
||||||
internal const Char StartParameter = '(';
|
|
||||||
internal const Char EndIndex = ']';
|
|
||||||
internal const Char EndParameter = ')';
|
|
||||||
internal const Char Separator = ',';
|
|
||||||
internal const Char Dereference = '.';
|
|
||||||
internal const Char Wildcard = '*';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public class ExpressionException : Exception
|
|
||||||
{
|
|
||||||
internal ExpressionException(ISecretMasker secretMasker, String message)
|
|
||||||
{
|
|
||||||
if (secretMasker != null)
|
|
||||||
{
|
|
||||||
message = secretMasker.MaskSecrets(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override String Message => m_message;
|
|
||||||
|
|
||||||
private readonly String m_message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,494 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public abstract class ExpressionNode : IExpressionNode
|
|
||||||
{
|
|
||||||
internal ContainerNode Container { get; set; }
|
|
||||||
|
|
||||||
internal Int32 Level { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name is used for tracing. Normally the parser will set the name. However if a node
|
|
||||||
/// is added manually, then the name may not be set and will fallback to the type name.
|
|
||||||
/// </summary>
|
|
||||||
protected internal String Name
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return !String.IsNullOrEmpty(m_name) ? m_name : this.GetType().Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_name = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the evalation result should be stored on the context and used
|
|
||||||
/// when the realized result is traced.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract Boolean TraceFullyRealized { get; }
|
|
||||||
|
|
||||||
internal abstract String ConvertToExpression();
|
|
||||||
|
|
||||||
internal abstract String ConvertToRealizedExpression(EvaluationContext context);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the node
|
|
||||||
/// </summary>
|
|
||||||
protected virtual Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Method {nameof(EvaluateCore)} not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the node
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The current expression context</param>
|
|
||||||
/// <param name="resultMemory">
|
|
||||||
/// Helps determine how much memory is being consumed across the evaluation of the expression.
|
|
||||||
/// </param>
|
|
||||||
protected virtual Object EvaluateCore(
|
|
||||||
EvaluationContext context,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return EvaluateCore(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// INode entry point.
|
|
||||||
/// </summary>
|
|
||||||
public T Evaluate<T>(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options = null)
|
|
||||||
{
|
|
||||||
if (Container != null)
|
|
||||||
{
|
|
||||||
// Do not localize. This is an SDK consumer error.
|
|
||||||
throw new NotSupportedException($"Expected {nameof(IExpressionNode)}.{nameof(Evaluate)} to be called on root node only.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ISecretMasker originalSecretMasker = secretMasker;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
secretMasker = secretMasker?.Clone() ?? new SecretMasker();
|
|
||||||
trace = new EvaluationTraceWriter(trace, secretMasker);
|
|
||||||
var context = new EvaluationContext(trace, secretMasker, state, options, this);
|
|
||||||
trace.Info($"Evaluating: {ConvertToExpression()}");
|
|
||||||
|
|
||||||
// String
|
|
||||||
if (typeof(T).Equals(typeof(String)))
|
|
||||||
{
|
|
||||||
String stringResult = EvaluateString(context);
|
|
||||||
TraceTreeResult(context, stringResult, ValueKind.String);
|
|
||||||
return (T)(Object)stringResult;
|
|
||||||
}
|
|
||||||
// Boolean
|
|
||||||
else if (typeof(T).Equals(typeof(Boolean)))
|
|
||||||
{
|
|
||||||
Boolean booleanResult = EvaluateBoolean(context);
|
|
||||||
TraceTreeResult(context, booleanResult, ValueKind.Boolean);
|
|
||||||
return (T)(Object)booleanResult;
|
|
||||||
}
|
|
||||||
// Version
|
|
||||||
else if (typeof(T).Equals(typeof(Version)))
|
|
||||||
{
|
|
||||||
Version versionResult = EvaluateVersion(context);
|
|
||||||
TraceTreeResult(context, versionResult, ValueKind.Version);
|
|
||||||
return (T)(Object)versionResult;
|
|
||||||
}
|
|
||||||
// DateTime types
|
|
||||||
else if (typeof(T).Equals(typeof(DateTimeOffset)))
|
|
||||||
{
|
|
||||||
DateTimeOffset dateTimeResult = EvaluateDateTime(context);
|
|
||||||
TraceTreeResult(context, dateTimeResult, ValueKind.DateTime);
|
|
||||||
return (T)(Object)dateTimeResult;
|
|
||||||
}
|
|
||||||
else if (typeof(T).Equals(typeof(DateTime)))
|
|
||||||
{
|
|
||||||
DateTimeOffset dateTimeResult = EvaluateDateTime(context);
|
|
||||||
TraceTreeResult(context, dateTimeResult, ValueKind.DateTime);
|
|
||||||
return (T)(Object)dateTimeResult.UtcDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeInfo typeInfo = typeof(T).GetTypeInfo();
|
|
||||||
if (typeInfo.IsPrimitive)
|
|
||||||
{
|
|
||||||
// Decimal
|
|
||||||
if (typeof(T).Equals(typeof(Decimal)))
|
|
||||||
{
|
|
||||||
Decimal decimalResult = EvaluateNumber(context);
|
|
||||||
TraceTreeResult(context, decimalResult, ValueKind.Number);
|
|
||||||
return (T)(Object)decimalResult;
|
|
||||||
}
|
|
||||||
// Other numeric types
|
|
||||||
else if (typeof(T).Equals(typeof(Byte)) ||
|
|
||||||
typeof(T).Equals(typeof(SByte)) ||
|
|
||||||
typeof(T).Equals(typeof(Int16)) ||
|
|
||||||
typeof(T).Equals(typeof(UInt16)) ||
|
|
||||||
typeof(T).Equals(typeof(Int32)) ||
|
|
||||||
typeof(T).Equals(typeof(UInt32)) ||
|
|
||||||
typeof(T).Equals(typeof(Int64)) ||
|
|
||||||
typeof(T).Equals(typeof(UInt64)) ||
|
|
||||||
typeof(T).Equals(typeof(Single)) ||
|
|
||||||
typeof(T).Equals(typeof(Double)))
|
|
||||||
{
|
|
||||||
Decimal decimalResult = EvaluateNumber(context);
|
|
||||||
trace.Verbose($"Converting expression result to type {typeof(T).Name}");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
T numericResult = (T)Convert.ChangeType(decimalResult, typeof(T));
|
|
||||||
|
|
||||||
// Note, the value is converted back to decimal before tracing, in order to leverage the same
|
|
||||||
// util-formatting method used in other places.
|
|
||||||
TraceTreeResult(context, Convert.ToDecimal((Object)numericResult), ValueKind.Number);
|
|
||||||
|
|
||||||
return numericResult;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
context.Trace.Verbose($"Failed to convert the result number into the type {typeof(T).Name}. {exception.Message}");
|
|
||||||
throw new TypeCastException(
|
|
||||||
secretMasker,
|
|
||||||
value: decimalResult,
|
|
||||||
fromKind: ValueKind.Number,
|
|
||||||
toType: typeof(T),
|
|
||||||
error: exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic evaluate
|
|
||||||
EvaluationResult result = Evaluate(context);
|
|
||||||
TraceTreeResult(context, result.Value, result.Kind);
|
|
||||||
|
|
||||||
// JToken
|
|
||||||
if (typeof(T).Equals(typeof(JToken)))
|
|
||||||
{
|
|
||||||
if (result.Value is null)
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
else if (result.Value is JToken)
|
|
||||||
{
|
|
||||||
return (T)result.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (T)(Object)JToken.FromObject(result.Value, JsonUtility.CreateJsonSerializer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Object or Array
|
|
||||||
else if (result.Kind == ValueKind.Object || result.Kind == ValueKind.Array)
|
|
||||||
{
|
|
||||||
Type resultType = result.Value.GetType();
|
|
||||||
context.Trace.Verbose($"Result type: {resultType.Name}");
|
|
||||||
if (typeInfo.IsAssignableFrom(resultType.GetTypeInfo()))
|
|
||||||
{
|
|
||||||
return (T)result.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Verbose($"Unable to assign result to the type {typeof(T).Name}");
|
|
||||||
throw new TypeCastException(fromType: resultType, toType: typeof(T));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Null
|
|
||||||
else if (result.Kind == ValueKind.Null)
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
// String
|
|
||||||
else if (result.Kind == ValueKind.String)
|
|
||||||
{
|
|
||||||
// Treat empty string as null
|
|
||||||
String stringResult = result.Value as String;
|
|
||||||
if (String.IsNullOrEmpty(stringResult))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise deserialize
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return JsonUtility.FromString<T>(stringResult);
|
|
||||||
}
|
|
||||||
catch (Exception exception) when (exception is JsonReaderException || exception is JsonSerializationException)
|
|
||||||
{
|
|
||||||
context.Trace.Verbose($"Failed to json-deserialize the result string into the type {typeof(T).Name}. {exception.Message}");
|
|
||||||
throw new TypeCastException(
|
|
||||||
context.SecretMasker,
|
|
||||||
value: stringResult,
|
|
||||||
fromKind: ValueKind.String,
|
|
||||||
toType: typeof(T),
|
|
||||||
error: exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Verbose($"Unable to convert from kind {result.Kind} to the type {typeof(T).Name}");
|
|
||||||
throw new TypeCastException(
|
|
||||||
context.SecretMasker,
|
|
||||||
value: result.Value,
|
|
||||||
fromKind: result.Kind,
|
|
||||||
toType: typeof(T));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (secretMasker != null && secretMasker != originalSecretMasker)
|
|
||||||
{
|
|
||||||
(secretMasker as IDisposable)?.Dispose();
|
|
||||||
secretMasker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// INode entry point.
|
|
||||||
/// </summary>
|
|
||||||
public Object Evaluate(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options = null)
|
|
||||||
{
|
|
||||||
return Evaluate(trace, secretMasker, state, options, out _, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// INode entry point.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean EvaluateBoolean(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state)
|
|
||||||
{
|
|
||||||
return Evaluate<Boolean>(trace, secretMasker, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// INode entry point.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public EvaluationResult EvaluateResult(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options)
|
|
||||||
{
|
|
||||||
var val = Evaluate(trace, secretMasker, state, options, out ValueKind kind, out Object raw);
|
|
||||||
return new EvaluationResult(null, 0, val, kind, raw, omitTracing: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call. The EvaluationContext
|
|
||||||
/// caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public EvaluationResult Evaluate(EvaluationContext context)
|
|
||||||
{
|
|
||||||
// Evaluate
|
|
||||||
Level = Container == null ? 0 : Container.Level + 1;
|
|
||||||
TraceVerbose(context, Level, $"Evaluating {Name}:");
|
|
||||||
var coreResult = EvaluateCore(context, out ResultMemory coreMemory);
|
|
||||||
|
|
||||||
if (coreMemory == null)
|
|
||||||
{
|
|
||||||
coreMemory = new ResultMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to canonical value
|
|
||||||
var val = ExpressionUtil.ConvertToCanonicalValue(context.Options, coreResult, out ValueKind kind, out Object raw, out ResultMemory conversionMemory);
|
|
||||||
|
|
||||||
// The depth can be safely trimmed when the total size of the core result is known,
|
|
||||||
// or when the total size of the core result can easily be determined.
|
|
||||||
var trimDepth = coreMemory.IsTotal || (Object.ReferenceEquals(raw, null) && s_simpleKinds.Contains(kind));
|
|
||||||
|
|
||||||
// Account for the memory overhead of the core result
|
|
||||||
var coreBytes = coreMemory.Bytes ?? EvaluationMemory.CalculateBytes(raw ?? val);
|
|
||||||
context.Memory.AddAmount(Level, coreBytes, trimDepth);
|
|
||||||
|
|
||||||
// Account for the memory overhead of the conversion result
|
|
||||||
if (!Object.ReferenceEquals(raw, null))
|
|
||||||
{
|
|
||||||
if (conversionMemory == null)
|
|
||||||
{
|
|
||||||
conversionMemory = new ResultMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
var conversionBytes = conversionMemory.Bytes ?? EvaluationMemory.CalculateBytes(val);
|
|
||||||
context.Memory.AddAmount(Level, conversionBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new EvaluationResult(context, Level, val, kind, raw);
|
|
||||||
|
|
||||||
// Store the trace result
|
|
||||||
if (this.TraceFullyRealized)
|
|
||||||
{
|
|
||||||
context.SetTraceResult(this, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call during evaluation.
|
|
||||||
/// The EvaluationContext caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean EvaluateBoolean(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Evaluate(context).ConvertToBoolean(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call during evaluation.
|
|
||||||
/// The EvaluationContext caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public DateTimeOffset EvaluateDateTime(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Evaluate(context).ConvertToDateTime(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call during evaluation.
|
|
||||||
/// The EvaluationContext caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public Decimal EvaluateNumber(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Evaluate(context).ConvertToNumber(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call during evaluation.
|
|
||||||
/// The EvaluationContext caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public String EvaluateString(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Evaluate(context).ConvertToString(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is intended only for ExpressionNode authors to call during evaluation.
|
|
||||||
/// The EvaluationContext caches result-state specific to the evaluation instance.
|
|
||||||
/// </summary>
|
|
||||||
public Version EvaluateVersion(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Evaluate(context).ConvertToVersion(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IEnumerable<T> GetParameters<T>() where T : IExpressionNode
|
|
||||||
{
|
|
||||||
return new T[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MemoryCounter CreateMemoryCounter(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return new MemoryCounter(this, context.Options.MaxMemory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object Evaluate(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options,
|
|
||||||
out ValueKind kind,
|
|
||||||
out Object raw)
|
|
||||||
{
|
|
||||||
if (Container != null)
|
|
||||||
{
|
|
||||||
// Do not localize. This is an SDK consumer error.
|
|
||||||
throw new NotSupportedException($"Expected {nameof(IExpressionNode)}.{nameof(Evaluate)} to be called on root node only.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ISecretMasker originalSecretMasker = secretMasker;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Evaluate
|
|
||||||
secretMasker = secretMasker?.Clone() ?? new SecretMasker();
|
|
||||||
trace = new EvaluationTraceWriter(trace, secretMasker);
|
|
||||||
var context = new EvaluationContext(trace, secretMasker, state, options, this);
|
|
||||||
trace.Info($"Evaluating: {ConvertToExpression()}");
|
|
||||||
EvaluationResult result = Evaluate(context);
|
|
||||||
|
|
||||||
// Trace the result
|
|
||||||
TraceTreeResult(context, result.Value, result.Kind);
|
|
||||||
|
|
||||||
kind = result.Kind;
|
|
||||||
raw = result.Raw;
|
|
||||||
return result.Value;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (secretMasker != null && secretMasker != originalSecretMasker)
|
|
||||||
{
|
|
||||||
(secretMasker as IDisposable)?.Dispose();
|
|
||||||
secretMasker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraceTreeResult(
|
|
||||||
EvaluationContext context,
|
|
||||||
Object result,
|
|
||||||
ValueKind kind)
|
|
||||||
{
|
|
||||||
// Get the realized expression
|
|
||||||
String realizedExpression = ConvertToRealizedExpression(context);
|
|
||||||
|
|
||||||
// Format the result
|
|
||||||
String traceValue = ExpressionUtil.FormatValue(context.SecretMasker, result, kind);
|
|
||||||
|
|
||||||
// Only trace the realized expression if it is meaningfully different
|
|
||||||
if (!String.Equals(realizedExpression, traceValue, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (kind == ValueKind.Number &&
|
|
||||||
String.Equals(realizedExpression, $"'{traceValue}'", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
// Don't bother tracing the realized expression when the result is a number and the
|
|
||||||
// realized expresion is a precisely matching string.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Info($"Expanded: {realizedExpression}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always trace the result
|
|
||||||
context.Trace.Info($"Result: {traceValue}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void TraceVerbose(
|
|
||||||
EvaluationContext context,
|
|
||||||
Int32 level,
|
|
||||||
String message)
|
|
||||||
{
|
|
||||||
context.Trace.Verbose(String.Empty.PadLeft(level * 2, '.') + (message ?? String.Empty));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly ValueKind[] s_simpleKinds = new[]
|
|
||||||
{
|
|
||||||
ValueKind.Boolean,
|
|
||||||
ValueKind.DateTime,
|
|
||||||
ValueKind.Null,
|
|
||||||
ValueKind.Number,
|
|
||||||
ValueKind.String,
|
|
||||||
ValueKind.Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
private String m_name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,547 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class ExpressionParser
|
|
||||||
{
|
|
||||||
public ExpressionParser(): this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExpressionParser(ExpressionParserOptions options)
|
|
||||||
{
|
|
||||||
m_parserOptions = options ?? new ExpressionParserOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IExpressionNode CreateTree(
|
|
||||||
String expression,
|
|
||||||
ITraceWriter trace,
|
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
|
||||||
IEnumerable<IFunctionInfo> functions)
|
|
||||||
{
|
|
||||||
var context = new ParseContext(expression, trace, namedValues, functions, allowUnknownKeywords: false, allowKeywordHyphens: m_parserOptions.AllowHyphens);
|
|
||||||
context.Trace.Info($"Parsing expression: <{expression}>");
|
|
||||||
return CreateTree(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ValidateSyntax(
|
|
||||||
String expression,
|
|
||||||
ITraceWriter trace)
|
|
||||||
{
|
|
||||||
var context = new ParseContext(expression, trace, namedValues: null, functions: null, allowUnknownKeywords: true, allowKeywordHyphens: m_parserOptions.AllowHyphens);
|
|
||||||
context.Trace.Info($"Validating expression syntax: <{expression}>");
|
|
||||||
CreateTree(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IExpressionNode CreateTree(ParseContext context)
|
|
||||||
{
|
|
||||||
while (TryGetNextToken(context))
|
|
||||||
{
|
|
||||||
switch (context.Token.Kind)
|
|
||||||
{
|
|
||||||
// Punctuation
|
|
||||||
case TokenKind.StartIndex:
|
|
||||||
HandleStartIndex(context);
|
|
||||||
break;
|
|
||||||
case TokenKind.EndIndex:
|
|
||||||
HandleEndIndex(context);
|
|
||||||
break;
|
|
||||||
case TokenKind.EndParameter:
|
|
||||||
HandleEndParameter(context);
|
|
||||||
break;
|
|
||||||
case TokenKind.Separator:
|
|
||||||
HandleSeparator(context);
|
|
||||||
break;
|
|
||||||
case TokenKind.Dereference:
|
|
||||||
HandleDereference(context);
|
|
||||||
break;
|
|
||||||
case TokenKind.Wildcard:
|
|
||||||
HandleWildcard(context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
case TokenKind.WellKnownFunction:
|
|
||||||
case TokenKind.ExtensionFunction:
|
|
||||||
HandleFunction(context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Leaf values
|
|
||||||
case TokenKind.Boolean:
|
|
||||||
case TokenKind.Number:
|
|
||||||
case TokenKind.Version:
|
|
||||||
case TokenKind.String:
|
|
||||||
case TokenKind.ExtensionNamedValue:
|
|
||||||
HandleValue(context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Unknown keyword
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
HandleUnknownKeyword(context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Malformed
|
|
||||||
case TokenKind.Unrecognized:
|
|
||||||
throw new ParseException(ParseExceptionKind.UnrecognizedValue, context.Token, context.Expression);
|
|
||||||
|
|
||||||
// Unexpected
|
|
||||||
case TokenKind.PropertyName: // PropertyName should never reach here (HandleDereference reads next token).
|
|
||||||
case TokenKind.StartParameter: // StartParameter is only expected by HandleFunction.
|
|
||||||
default:
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate depth.
|
|
||||||
if (context.Containers.Count >= ExpressionConstants.MaxDepth)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.ExceededMaxDepth, token: null, expression: context.Expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate all containers were closed.
|
|
||||||
if (context.Containers.Count > 0)
|
|
||||||
{
|
|
||||||
ContainerInfo container = context.Containers.Peek();
|
|
||||||
if (container.Node is FunctionNode)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnclosedFunction, container.Token, context.Expression);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnclosedIndexer, container.Token, context.Expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.Root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryGetNextToken(ParseContext context)
|
|
||||||
{
|
|
||||||
context.LastToken = context.Token;
|
|
||||||
if (context.Lexer.TryGetNextToken(ref context.Token))
|
|
||||||
{
|
|
||||||
// Adjust indent level.
|
|
||||||
int indentLevel = context.Containers.Count;
|
|
||||||
if (indentLevel > 0)
|
|
||||||
{
|
|
||||||
switch (context.Token.Kind)
|
|
||||||
{
|
|
||||||
case TokenKind.StartParameter:
|
|
||||||
case TokenKind.EndParameter:
|
|
||||||
case TokenKind.EndIndex:
|
|
||||||
indentLevel--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String indent = String.Empty.PadRight(indentLevel * 2, '.');
|
|
||||||
switch (context.Token.Kind)
|
|
||||||
{
|
|
||||||
// Literal values
|
|
||||||
case TokenKind.Boolean:
|
|
||||||
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Boolean)}");
|
|
||||||
break;
|
|
||||||
case TokenKind.Number:
|
|
||||||
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Number)}");
|
|
||||||
break;
|
|
||||||
case TokenKind.Version:
|
|
||||||
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Version)}");
|
|
||||||
break;
|
|
||||||
case TokenKind.String:
|
|
||||||
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.String)}");
|
|
||||||
break;
|
|
||||||
// Property or unrecognized
|
|
||||||
case TokenKind.PropertyName:
|
|
||||||
case TokenKind.Unrecognized:
|
|
||||||
context.Trace.Verbose($"{indent}{context.Token.Kind} {ExpressionUtil.FormatValue(null, context.Token.RawValue, ValueKind.String)}");
|
|
||||||
break;
|
|
||||||
// Function or punctuation
|
|
||||||
case TokenKind.WellKnownFunction:
|
|
||||||
case TokenKind.ExtensionFunction:
|
|
||||||
case TokenKind.ExtensionNamedValue:
|
|
||||||
case TokenKind.Wildcard:
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
case TokenKind.StartIndex:
|
|
||||||
case TokenKind.StartParameter:
|
|
||||||
case TokenKind.EndIndex:
|
|
||||||
case TokenKind.EndParameter:
|
|
||||||
case TokenKind.Separator:
|
|
||||||
case TokenKind.Dereference:
|
|
||||||
context.Trace.Verbose($"{indent}{context.Token.RawValue}");
|
|
||||||
break;
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unexpected token kind: {context.Token.Kind}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleStartIndex(ParseContext context)
|
|
||||||
{
|
|
||||||
// Validate follows ")", "]", "*", or a property name.
|
|
||||||
if (context.LastToken == null ||
|
|
||||||
(context.LastToken.Kind != TokenKind.EndParameter && context.LastToken.Kind != TokenKind.EndIndex && context.LastToken.Kind != TokenKind.PropertyName && context.LastToken.Kind != TokenKind.ExtensionNamedValue && context.LastToken.Kind != TokenKind.UnknownKeyword && context.LastToken.Kind != TokenKind.Wildcard))
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the object being indexed into.
|
|
||||||
var indexer = new IndexerNode();
|
|
||||||
ExpressionNode obj = null;
|
|
||||||
if (context.Containers.Count > 0)
|
|
||||||
{
|
|
||||||
ContainerNode container = context.Containers.Peek().Node;
|
|
||||||
Int32 objIndex = container.Parameters.Count - 1;
|
|
||||||
obj = container.Parameters[objIndex];
|
|
||||||
container.ReplaceParameter(objIndex, indexer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
obj = context.Root;
|
|
||||||
context.Root = indexer;
|
|
||||||
}
|
|
||||||
|
|
||||||
indexer.AddParameter(obj);
|
|
||||||
|
|
||||||
// Update the container stack.
|
|
||||||
context.Containers.Push(new ContainerInfo() { Node = indexer, Token = context.Token });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleDereference(ParseContext context)
|
|
||||||
{
|
|
||||||
// Validate follows ")", "]", "*", or a property name.
|
|
||||||
if (context.LastToken == null ||
|
|
||||||
(context.LastToken.Kind != TokenKind.EndParameter && context.LastToken.Kind != TokenKind.EndIndex && context.LastToken.Kind != TokenKind.PropertyName && context.LastToken.Kind != TokenKind.ExtensionNamedValue && context.LastToken.Kind != TokenKind.UnknownKeyword && context.LastToken.Kind != TokenKind.Wildcard))
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the object being indexed into.
|
|
||||||
var indexer = new IndexerNode();
|
|
||||||
ExpressionNode obj = null;
|
|
||||||
if (context.Containers.Count > 0)
|
|
||||||
{
|
|
||||||
ContainerNode container = context.Containers.Peek().Node;
|
|
||||||
Int32 objIndex = container.Parameters.Count - 1;
|
|
||||||
obj = container.Parameters[objIndex];
|
|
||||||
container.ReplaceParameter(objIndex, indexer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
obj = context.Root;
|
|
||||||
context.Root = indexer;
|
|
||||||
}
|
|
||||||
|
|
||||||
indexer.AddParameter(obj);
|
|
||||||
|
|
||||||
// Validate a token follows.
|
|
||||||
if (!TryGetNextToken(context))
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.ExpectedPropertyName, context.LastToken, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.Token.Kind == TokenKind.PropertyName)
|
|
||||||
{
|
|
||||||
indexer.AddParameter(new LiteralValueNode(context.Token.RawValue));
|
|
||||||
}
|
|
||||||
else if (context.Token.Kind == TokenKind.Wildcard)
|
|
||||||
{
|
|
||||||
// For a wildcard we add a third parameter, a boolean set to true, so that we know it's a wildcard.
|
|
||||||
indexer.AddParameter(new LiteralValueNode(context.Token.RawValue));
|
|
||||||
indexer.AddParameter(new LiteralValueNode(true));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleWildcard(ParseContext context)
|
|
||||||
{
|
|
||||||
// Validate follows "[".
|
|
||||||
if (context.LastToken == null ||
|
|
||||||
context.LastToken.Kind != TokenKind.StartIndex)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we have a wildcard, we add the wildcard and also third boolean parameter set to true.
|
|
||||||
// This lets us differentiate downstream from '*'.
|
|
||||||
context.Containers.Peek().Node.AddParameter(new LiteralValueNode(context.Token.RawValue));
|
|
||||||
context.Containers.Peek().Node.AddParameter(new LiteralValueNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleEndParameter(ParseContext context)
|
|
||||||
{
|
|
||||||
ContainerInfo container = context.Containers.Count > 0 ? context.Containers.Peek() : null; // Validate:
|
|
||||||
if (container == null || // 1) Container is not null
|
|
||||||
!(container.Node is FunctionNode) || // 2) Container is a function
|
|
||||||
container.Node.Parameters.Count < GetMinParamCount(context, container.Token) || // 3) Not below min param threshold
|
|
||||||
container.Node.Parameters.Count > GetMaxParamCount(context, container.Token) || // 4) Not above max param threshold
|
|
||||||
context.LastToken.Kind == TokenKind.Separator) // 5) Last token is not a separator
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Containers.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleEndIndex(ParseContext context)
|
|
||||||
{
|
|
||||||
IndexerNode indexer = context.Containers.Count > 0 ? context.Containers.Peek().Node as IndexerNode : null;
|
|
||||||
// // Validate:
|
|
||||||
if (indexer == null || // 1) Container is an indexer
|
|
||||||
!(indexer.Parameters.Count == 2 || indexer.Parameters.Count == 3)) // 2) Can be 2 or 3 parameters. It's 3 parameters when we are using a filtered array since we
|
|
||||||
// set a boolean along with the wildcard.
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Containers.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleUnknownKeyword(ParseContext context)
|
|
||||||
{
|
|
||||||
// Validate.
|
|
||||||
if (!context.AllowUnknownKeywords)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnrecognizedValue, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try handle function.
|
|
||||||
if (HandleFunction(context, bestEffort: true))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle named value.
|
|
||||||
HandleValue(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleValue(ParseContext context)
|
|
||||||
{
|
|
||||||
// Validate either A) is the first token OR B) follows "[" "(" or ",".
|
|
||||||
if (context.LastToken != null &&
|
|
||||||
context.LastToken.Kind != TokenKind.StartIndex &&
|
|
||||||
context.LastToken.Kind != TokenKind.StartParameter &&
|
|
||||||
context.LastToken.Kind != TokenKind.Separator)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the node.
|
|
||||||
ExpressionNode node;
|
|
||||||
switch (context.Token.Kind)
|
|
||||||
{
|
|
||||||
case TokenKind.ExtensionNamedValue:
|
|
||||||
String name = context.Token.RawValue;
|
|
||||||
node = context.ExtensionNamedValues[name].CreateNode();
|
|
||||||
node.Name = name;
|
|
||||||
break;
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
node = new UnknownNamedValueNode();
|
|
||||||
node.Name = context.Token.RawValue;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
node = new LiteralValueNode(context.Token.ParsedValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tree.
|
|
||||||
if (context.Root == null)
|
|
||||||
{
|
|
||||||
context.Root = node;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Containers.Peek().Node.AddParameter(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleSeparator(ParseContext context)
|
|
||||||
{
|
|
||||||
ContainerInfo container = context.Containers.Count > 0 ? context.Containers.Peek() : null; // Validate:
|
|
||||||
if (container == null || // 1) Container is not null
|
|
||||||
!(container.Node is FunctionNode) || // 2) Container is a function
|
|
||||||
container.Node.Parameters.Count < 1 || // 3) At least one parameter
|
|
||||||
container.Node.Parameters.Count >= GetMaxParamCount(context, container.Token) ||// 4) Under max parameters threshold
|
|
||||||
context.LastToken.Kind == TokenKind.Separator) // 5) Last token is not a separator
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean HandleFunction(
|
|
||||||
ParseContext context,
|
|
||||||
Boolean bestEffort = false)
|
|
||||||
{
|
|
||||||
// Validate either A) is first token OR B) follows "," or "[" or "(".
|
|
||||||
if (context.LastToken != null &&
|
|
||||||
(context.LastToken.Kind != TokenKind.Separator &&
|
|
||||||
context.LastToken.Kind != TokenKind.StartIndex &&
|
|
||||||
context.LastToken.Kind != TokenKind.StartParameter))
|
|
||||||
{
|
|
||||||
if (bestEffort)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate '(' follows.
|
|
||||||
if (bestEffort)
|
|
||||||
{
|
|
||||||
Token nextToken = null;
|
|
||||||
if (!context.Lexer.TryPeekNextToken(ref nextToken) || nextToken.Kind != TokenKind.StartParameter)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryGetNextToken(context);
|
|
||||||
}
|
|
||||||
else if (!TryGetNextToken(context) || context.Token.Kind != TokenKind.StartParameter)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.ExpectedStartParameter, context.LastToken, context.Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the node.
|
|
||||||
FunctionNode node;
|
|
||||||
String name = context.LastToken.RawValue;
|
|
||||||
switch (context.LastToken.Kind)
|
|
||||||
{
|
|
||||||
case TokenKind.WellKnownFunction:
|
|
||||||
node = ExpressionConstants.WellKnownFunctions[name].CreateNode();
|
|
||||||
node.Name = name;
|
|
||||||
break;
|
|
||||||
case TokenKind.ExtensionFunction:
|
|
||||||
node = context.ExtensionFunctions[name].CreateNode();
|
|
||||||
node.Name = name;
|
|
||||||
break;
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
node = new UnknownFunctionNode();
|
|
||||||
node.Name = name;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unexpected function token name: '{context.LastToken.Kind}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tree.
|
|
||||||
if (context.Root == null)
|
|
||||||
{
|
|
||||||
context.Root = node;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Containers.Peek().Node.AddParameter(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the container stack.
|
|
||||||
context.Containers.Push(new ContainerInfo() { Node = node, Token = context.LastToken });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetMinParamCount(
|
|
||||||
ParseContext context,
|
|
||||||
Token token)
|
|
||||||
{
|
|
||||||
switch (token.Kind)
|
|
||||||
{
|
|
||||||
case TokenKind.WellKnownFunction:
|
|
||||||
return ExpressionConstants.WellKnownFunctions[token.RawValue].MinParameters;
|
|
||||||
case TokenKind.ExtensionFunction:
|
|
||||||
return context.ExtensionFunctions[token.RawValue].MinParameters;
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
return 0;
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unexpected token kind '{token.Kind}'. Unable to determine min param count.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Int32 GetMaxParamCount(
|
|
||||||
ParseContext context,
|
|
||||||
Token token)
|
|
||||||
{
|
|
||||||
switch (token.Kind)
|
|
||||||
{
|
|
||||||
case TokenKind.WellKnownFunction:
|
|
||||||
return ExpressionConstants.WellKnownFunctions[token.RawValue].MaxParameters;
|
|
||||||
case TokenKind.ExtensionFunction:
|
|
||||||
return context.ExtensionFunctions[token.RawValue].MaxParameters;
|
|
||||||
case TokenKind.UnknownKeyword:
|
|
||||||
return Int32.MaxValue;
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unexpected token kind '{token.Kind}'. Unable to determine max param count.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionParserOptions m_parserOptions;
|
|
||||||
|
|
||||||
private sealed class ContainerInfo
|
|
||||||
{
|
|
||||||
public ContainerNode Node { get; set; }
|
|
||||||
|
|
||||||
public Token Token { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ParseContext
|
|
||||||
{
|
|
||||||
public readonly Boolean AllowUnknownKeywords;
|
|
||||||
public readonly Stack<ContainerInfo> Containers = new Stack<ContainerInfo>();
|
|
||||||
public readonly String Expression;
|
|
||||||
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public readonly LexicalAnalyzer Lexer;
|
|
||||||
public readonly ITraceWriter Trace;
|
|
||||||
public Token Token;
|
|
||||||
public Token LastToken;
|
|
||||||
public ExpressionNode Root;
|
|
||||||
|
|
||||||
public ParseContext(
|
|
||||||
String expression,
|
|
||||||
ITraceWriter trace,
|
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
|
||||||
IEnumerable<IFunctionInfo> functions,
|
|
||||||
Boolean allowUnknownKeywords = false,
|
|
||||||
Boolean allowKeywordHyphens = false)
|
|
||||||
{
|
|
||||||
Expression = expression ?? String.Empty;
|
|
||||||
if (Expression.Length > ExpressionConstants.MaxLength)
|
|
||||||
{
|
|
||||||
throw new ParseException(ParseExceptionKind.ExceededMaxLength, token: null, expression: Expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace = trace ?? new NoOperationTraceWriter();
|
|
||||||
foreach (INamedValueInfo namedValueInfo in (namedValues ?? new INamedValueInfo[0]))
|
|
||||||
{
|
|
||||||
ExtensionNamedValues.Add(namedValueInfo.Name, namedValueInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (IFunctionInfo functionInfo in (functions ?? new IFunctionInfo[0]))
|
|
||||||
{
|
|
||||||
ExtensionFunctions.Add(functionInfo.Name, functionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
AllowUnknownKeywords = allowUnknownKeywords;
|
|
||||||
Lexer = new LexicalAnalyzer(Expression, namedValues: ExtensionNamedValues.Keys, functions: ExtensionFunctions.Keys, allowKeywordHyphens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NoOperationTraceWriter : ITraceWriter
|
|
||||||
{
|
|
||||||
public void Info(String message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(String message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public sealed class ExpressionParserOptions
|
|
||||||
{
|
|
||||||
public Boolean AllowHyphens
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
internal static class ExpressionUtil
|
|
||||||
{
|
|
||||||
internal static Object ConvertToCanonicalValue(
|
|
||||||
EvaluationOptions options,
|
|
||||||
Object val,
|
|
||||||
out ValueKind kind,
|
|
||||||
out Object raw,
|
|
||||||
out ResultMemory conversionResultMemory)
|
|
||||||
{
|
|
||||||
// Apply converter
|
|
||||||
if (options?.Converters?.Count > 0 &&
|
|
||||||
!Object.ReferenceEquals(val, null) &&
|
|
||||||
options.Converters.TryGetValue(val.GetType(), out Converter<Object, ConversionResult> convert))
|
|
||||||
{
|
|
||||||
raw = val;
|
|
||||||
var conversionResult = convert(val);
|
|
||||||
val = conversionResult.Result;
|
|
||||||
conversionResultMemory = conversionResult.ResultMemory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
raw = null;
|
|
||||||
conversionResultMemory = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.ReferenceEquals(val, null))
|
|
||||||
{
|
|
||||||
kind = ValueKind.Null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (val is IString str)
|
|
||||||
{
|
|
||||||
kind = ValueKind.String;
|
|
||||||
return str.GetString();
|
|
||||||
}
|
|
||||||
else if (val is IBoolean booleanValue)
|
|
||||||
{
|
|
||||||
kind = ValueKind.Boolean;
|
|
||||||
return booleanValue.GetBoolean();
|
|
||||||
}
|
|
||||||
else if (val is INumber num)
|
|
||||||
{
|
|
||||||
kind = ValueKind.Number;
|
|
||||||
return num.GetNumber();
|
|
||||||
}
|
|
||||||
else if (val is JToken)
|
|
||||||
{
|
|
||||||
var jtoken = val as JToken;
|
|
||||||
switch (jtoken.Type)
|
|
||||||
{
|
|
||||||
case JTokenType.Array:
|
|
||||||
kind = ValueKind.Array;
|
|
||||||
return jtoken;
|
|
||||||
case JTokenType.Boolean:
|
|
||||||
kind = ValueKind.Boolean;
|
|
||||||
return jtoken.ToObject<Boolean>();
|
|
||||||
case JTokenType.Float:
|
|
||||||
case JTokenType.Integer:
|
|
||||||
kind = ValueKind.Number;
|
|
||||||
// todo: test the extents of the conversion
|
|
||||||
return jtoken.ToObject<Decimal>();
|
|
||||||
case JTokenType.Null:
|
|
||||||
kind = ValueKind.Null;
|
|
||||||
return null;
|
|
||||||
case JTokenType.Object:
|
|
||||||
kind = ValueKind.Object;
|
|
||||||
return jtoken;
|
|
||||||
case JTokenType.String:
|
|
||||||
kind = ValueKind.String;
|
|
||||||
return jtoken.ToObject<String>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (val is String)
|
|
||||||
{
|
|
||||||
kind = ValueKind.String;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
else if (val is Version)
|
|
||||||
{
|
|
||||||
kind = ValueKind.Version;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
else if (!val.GetType().GetTypeInfo().IsClass)
|
|
||||||
{
|
|
||||||
if (val is Boolean)
|
|
||||||
{
|
|
||||||
kind = ValueKind.Boolean;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
else if (val is DateTimeOffset)
|
|
||||||
{
|
|
||||||
kind = ValueKind.DateTime;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
else if (val is DateTime dateTime)
|
|
||||||
{
|
|
||||||
kind = ValueKind.DateTime;
|
|
||||||
switch (dateTime.Kind)
|
|
||||||
{
|
|
||||||
// When Local: convert to preferred time zone
|
|
||||||
case DateTimeKind.Local:
|
|
||||||
var targetTimeZone = options?.TimeZone ?? TimeZoneInfo.Local;
|
|
||||||
var localDateTimeOffset = new DateTimeOffset(dateTime);
|
|
||||||
return TimeZoneInfo.ConvertTime(localDateTimeOffset, targetTimeZone);
|
|
||||||
// When Unspecified: assume preferred time zone
|
|
||||||
case DateTimeKind.Unspecified:
|
|
||||||
var timeZone = options?.TimeZone ?? TimeZoneInfo.Local;
|
|
||||||
var offset = timeZone.GetUtcOffset(dateTime);
|
|
||||||
return new DateTimeOffset(dateTime, offset);
|
|
||||||
// When UTC: keep UTC
|
|
||||||
case DateTimeKind.Utc:
|
|
||||||
return new DateTimeOffset(dateTime);
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException($"Unexpected DateTimeKind '{dateTime.Kind}'"); // Should never happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (val is Decimal || val is Byte || val is SByte || val is Int16 || val is UInt16 || val is Int32 || val is UInt32 || val is Int64 || val is UInt64 || val is Single || val is Double)
|
|
||||||
{
|
|
||||||
kind = ValueKind.Number;
|
|
||||||
return Convert.ToDecimal(val);
|
|
||||||
}
|
|
||||||
else if (val is Enum)
|
|
||||||
{
|
|
||||||
var strVal = String.Format(CultureInfo.InvariantCulture, "{0:G}", val);
|
|
||||||
if (Decimal.TryParse(strVal, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out Decimal decVal))
|
|
||||||
{
|
|
||||||
kind = ValueKind.Number;
|
|
||||||
return decVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = ValueKind.String;
|
|
||||||
return strVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = ValueKind.Object;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static String FormatValue(
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
EvaluationResult evaluationResult)
|
|
||||||
{
|
|
||||||
return FormatValue(secretMasker, evaluationResult.Value, evaluationResult.Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static String FormatValue(
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object value,
|
|
||||||
ValueKind kind)
|
|
||||||
{
|
|
||||||
switch (kind)
|
|
||||||
{
|
|
||||||
case ValueKind.Boolean:
|
|
||||||
return ((Boolean)value).ToString();
|
|
||||||
|
|
||||||
case ValueKind.DateTime:
|
|
||||||
var strDateTime = "(DateTime)" + ((DateTimeOffset)value).ToString(ExpressionConstants.DateTimeFormat, CultureInfo.InvariantCulture);
|
|
||||||
return secretMasker != null ? secretMasker.MaskSecrets(strDateTime) : strDateTime;
|
|
||||||
|
|
||||||
case ValueKind.Number:
|
|
||||||
var strNumber = ((Decimal)value).ToString(ExpressionConstants.NumberFormat, CultureInfo.InvariantCulture);
|
|
||||||
return secretMasker != null ? secretMasker.MaskSecrets(strNumber) : strNumber;
|
|
||||||
|
|
||||||
case ValueKind.String:
|
|
||||||
// Mask secrets before string-escaping.
|
|
||||||
var strValue = secretMasker != null ? secretMasker.MaskSecrets(value as String) : value as String;
|
|
||||||
return $"'{StringEscape(strValue)}'";
|
|
||||||
|
|
||||||
case ValueKind.Version:
|
|
||||||
String strVersion = secretMasker != null ? secretMasker.MaskSecrets(value.ToString()) : value.ToString();
|
|
||||||
return $"v{strVersion}";
|
|
||||||
|
|
||||||
case ValueKind.Array:
|
|
||||||
case ValueKind.Null:
|
|
||||||
case ValueKind.Object:
|
|
||||||
return kind.ToString();
|
|
||||||
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new NotSupportedException($"Unable to convert to realized expression. Unexpected value kind: {kind}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Char SafeCharAt(
|
|
||||||
String str,
|
|
||||||
Int32 index)
|
|
||||||
{
|
|
||||||
if (str.Length > index)
|
|
||||||
{
|
|
||||||
return str[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static String StringEscape(String value)
|
|
||||||
{
|
|
||||||
return String.IsNullOrEmpty(value) ? String.Empty : value.Replace("'", "''");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class FormatNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
var format = Parameters[0].EvaluateString(context);
|
|
||||||
var index = 0;
|
|
||||||
var result = new FormatResultBuilder(this, context, CreateMemoryCounter(context));
|
|
||||||
while (index < format.Length)
|
|
||||||
{
|
|
||||||
var lbrace = format.IndexOf('{', index);
|
|
||||||
var rbrace = format.IndexOf('}', index);
|
|
||||||
|
|
||||||
// Left brace
|
|
||||||
if (lbrace >= 0 && (rbrace < 0 || rbrace > lbrace))
|
|
||||||
{
|
|
||||||
// Escaped left brace
|
|
||||||
if (ExpressionUtil.SafeCharAt(format, lbrace + 1) == '{')
|
|
||||||
{
|
|
||||||
result.Append(format.Substring(index, lbrace - index + 1));
|
|
||||||
index = lbrace + 2;
|
|
||||||
}
|
|
||||||
// Left brace, number, optional format specifiers, right brace
|
|
||||||
else if (rbrace > lbrace + 1 &&
|
|
||||||
ReadArgIndex(format, lbrace + 1, out Byte argIndex, out Int32 endArgIndex) &&
|
|
||||||
ReadFormatSpecifiers(format, endArgIndex + 1, out String formatSpecifiers, out rbrace))
|
|
||||||
{
|
|
||||||
// Check parameter count
|
|
||||||
if (argIndex > Parameters.Count - 2)
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatArgIndex(format));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the portion before the left brace
|
|
||||||
if (lbrace > index)
|
|
||||||
{
|
|
||||||
result.Append(format.Substring(index, lbrace - index));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the arg
|
|
||||||
result.Append(argIndex, formatSpecifiers);
|
|
||||||
index = rbrace + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatString(format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Right brace
|
|
||||||
else if (rbrace >= 0)
|
|
||||||
{
|
|
||||||
// Escaped right brace
|
|
||||||
if (ExpressionUtil.SafeCharAt(format, rbrace + 1) == '}')
|
|
||||||
{
|
|
||||||
result.Append(format.Substring(index, rbrace - index + 1));
|
|
||||||
index = rbrace + 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatString(format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Last segment
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Append(format.Substring(index));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean ReadArgIndex(
|
|
||||||
String str,
|
|
||||||
Int32 startIndex,
|
|
||||||
out Byte result,
|
|
||||||
out Int32 endIndex)
|
|
||||||
{
|
|
||||||
// Count the number of digits
|
|
||||||
var length = 0;
|
|
||||||
while (Char.IsDigit(ExpressionUtil.SafeCharAt(str, startIndex + length)))
|
|
||||||
{
|
|
||||||
length++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate at least one digit
|
|
||||||
if (length < 1)
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
endIndex = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the number
|
|
||||||
endIndex = startIndex + length - 1;
|
|
||||||
return Byte.TryParse(str.Substring(startIndex, length), NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean ReadFormatSpecifiers(
|
|
||||||
String str,
|
|
||||||
Int32 startIndex,
|
|
||||||
out String result,
|
|
||||||
out Int32 rbrace)
|
|
||||||
{
|
|
||||||
// No format specifiers
|
|
||||||
var c = ExpressionUtil.SafeCharAt(str, startIndex);
|
|
||||||
if (c == '}')
|
|
||||||
{
|
|
||||||
result = String.Empty;
|
|
||||||
rbrace = startIndex;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate starts with ":"
|
|
||||||
if (c != ':')
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
rbrace = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the specifiers
|
|
||||||
var specifiers = new StringBuilder();
|
|
||||||
var index = startIndex + 1;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Validate not the end of the string
|
|
||||||
if (index >= str.Length)
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
rbrace = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
c = str[index];
|
|
||||||
|
|
||||||
// Not right-brace
|
|
||||||
if (c != '}')
|
|
||||||
{
|
|
||||||
specifiers.Append(c);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
// Escaped right-brace
|
|
||||||
else if (ExpressionUtil.SafeCharAt(str, index + 1) == '}')
|
|
||||||
{
|
|
||||||
specifiers.Append('}');
|
|
||||||
index += 2;
|
|
||||||
}
|
|
||||||
// Closing right-brace
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = specifiers.ToString();
|
|
||||||
rbrace = index;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FormatResultBuilder
|
|
||||||
{
|
|
||||||
internal FormatResultBuilder(
|
|
||||||
FormatNode node,
|
|
||||||
EvaluationContext context,
|
|
||||||
MemoryCounter counter)
|
|
||||||
{
|
|
||||||
m_node = node;
|
|
||||||
m_context = context;
|
|
||||||
m_counter = counter;
|
|
||||||
m_cache = new ArgValue[node.Parameters.Count - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the final string. This is when lazy segments are evaluated.
|
|
||||||
public override String ToString()
|
|
||||||
{
|
|
||||||
return String.Join(
|
|
||||||
String.Empty,
|
|
||||||
m_segments.Select(obj =>
|
|
||||||
{
|
|
||||||
if (obj is Lazy<String> lazy)
|
|
||||||
{
|
|
||||||
return lazy.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return obj as String;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append a static value
|
|
||||||
internal void Append(String value)
|
|
||||||
{
|
|
||||||
if (value?.Length > 0)
|
|
||||||
{
|
|
||||||
// Track memory
|
|
||||||
m_counter.Add(value);
|
|
||||||
|
|
||||||
// Append the segment
|
|
||||||
m_segments.Add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append an argument
|
|
||||||
internal void Append(
|
|
||||||
Int32 argIndex,
|
|
||||||
String formatSpecifiers)
|
|
||||||
{
|
|
||||||
// Delay execution until the final ToString
|
|
||||||
m_segments.Add(new Lazy<String>(() =>
|
|
||||||
{
|
|
||||||
String result;
|
|
||||||
|
|
||||||
// Get the arg from the cache
|
|
||||||
var argValue = m_cache[argIndex];
|
|
||||||
|
|
||||||
// Evaluate the arg and cache the result
|
|
||||||
if (argValue == null)
|
|
||||||
{
|
|
||||||
// The evaluation result is required when format specifiers are used. Otherwise the string
|
|
||||||
// result is required. Go ahead and store both values. Since ConvertToString produces tracing,
|
|
||||||
// we need to run that now so the tracing appears in order in the log.
|
|
||||||
var evaluationResult = m_node.Parameters[argIndex + 1].Evaluate(m_context);
|
|
||||||
var stringResult = evaluationResult.ConvertToString(m_context);
|
|
||||||
argValue = new ArgValue(evaluationResult, stringResult);
|
|
||||||
m_cache[argIndex] = argValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No format specifiers
|
|
||||||
if (String.IsNullOrEmpty(formatSpecifiers))
|
|
||||||
{
|
|
||||||
result = argValue.StringResult;
|
|
||||||
}
|
|
||||||
// DateTime
|
|
||||||
else if (argValue.EvaluationResult.Kind == ValueKind.DateTime)
|
|
||||||
{
|
|
||||||
result = FormatDateTime((DateTimeOffset)argValue.EvaluationResult.Value, formatSpecifiers);
|
|
||||||
}
|
|
||||||
// Invalid
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(formatSpecifiers, argValue.EvaluationResult.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track memory
|
|
||||||
if (!String.IsNullOrEmpty(result))
|
|
||||||
{
|
|
||||||
m_counter.Add(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String FormatDateTime(
|
|
||||||
DateTimeOffset dateTime,
|
|
||||||
String specifiers)
|
|
||||||
{
|
|
||||||
var result = new StringBuilder();
|
|
||||||
var i = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Get the next specifier
|
|
||||||
var specifier = GetNextSpecifier(specifiers, ref i);
|
|
||||||
|
|
||||||
// Check end of string
|
|
||||||
if (String.IsNullOrEmpty(specifier))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the value
|
|
||||||
switch (specifier)
|
|
||||||
{
|
|
||||||
case "yyyy":
|
|
||||||
case "yy":
|
|
||||||
case "MM":
|
|
||||||
case "dd":
|
|
||||||
case "HH":
|
|
||||||
case "mm":
|
|
||||||
case "ss":
|
|
||||||
case "ff":
|
|
||||||
case "fff":
|
|
||||||
case "ffff":
|
|
||||||
case "fffff":
|
|
||||||
case "ffffff":
|
|
||||||
case "fffffff":
|
|
||||||
case "zzz":
|
|
||||||
result.Append(dateTime.ToString(specifier));
|
|
||||||
break;
|
|
||||||
|
|
||||||
// .Net requires a leading % for some specifiers
|
|
||||||
case "M":
|
|
||||||
case "d":
|
|
||||||
case "H":
|
|
||||||
case "m":
|
|
||||||
case "s":
|
|
||||||
case "f":
|
|
||||||
case "K":
|
|
||||||
result.Append(dateTime.ToString("%" + specifier));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Escaped character
|
|
||||||
if (specifier[0] == '\\')
|
|
||||||
{
|
|
||||||
result.Append(specifier[1]);
|
|
||||||
}
|
|
||||||
else if (specifier[0] == ' ')
|
|
||||||
{
|
|
||||||
result.Append(specifier);
|
|
||||||
}
|
|
||||||
// Unexpected
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(specifiers, ValueKind.DateTime));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String GetNextSpecifier(
|
|
||||||
String specifiers,
|
|
||||||
ref Int32 index)
|
|
||||||
{
|
|
||||||
// End of string
|
|
||||||
if (index >= specifiers.Length)
|
|
||||||
{
|
|
||||||
return String.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the first char
|
|
||||||
var startIndex = index;
|
|
||||||
var c = specifiers[index++];
|
|
||||||
|
|
||||||
// Escaped
|
|
||||||
if (c == '\\')
|
|
||||||
{
|
|
||||||
// End of string
|
|
||||||
if (index >= specifiers.Length)
|
|
||||||
{
|
|
||||||
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(specifiers, ValueKind.DateTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
// Find consecutive matches
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (index < specifiers.Length && specifiers[index] == c)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return specifiers.Substring(startIndex, index - startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ArgValue[] m_cache;
|
|
||||||
private readonly EvaluationContext m_context;
|
|
||||||
private readonly MemoryCounter m_counter;
|
|
||||||
private readonly FormatNode m_node;
|
|
||||||
private readonly List<Object> m_segments = new List<Object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores an EvaluateResult and the value converted to a String.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class ArgValue
|
|
||||||
{
|
|
||||||
public ArgValue(
|
|
||||||
EvaluationResult evaluationResult,
|
|
||||||
String stringResult)
|
|
||||||
{
|
|
||||||
EvaluationResult = evaluationResult;
|
|
||||||
StringResult = stringResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EvaluationResult EvaluationResult { get; }
|
|
||||||
|
|
||||||
public String StringResult { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public class FunctionInfo<T> : IFunctionInfo
|
|
||||||
where T : FunctionNode, new()
|
|
||||||
{
|
|
||||||
public FunctionInfo(String name, Int32 minParameters, Int32 maxParameters)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
MinParameters = minParameters;
|
|
||||||
MaxParameters = maxParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String Name { get; }
|
|
||||||
|
|
||||||
public Int32 MinParameters { get; }
|
|
||||||
|
|
||||||
public Int32 MaxParameters { get; }
|
|
||||||
|
|
||||||
public FunctionNode CreateNode()
|
|
||||||
{
|
|
||||||
return new T();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public abstract class FunctionNode : ContainerNode
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generally this should not be overridden. True indicates the result of the node is traced as part of the "expanded"
|
|
||||||
/// (i.e. "realized") trace information. Otherwise the node expression is printed, and parameters to the node may or
|
|
||||||
/// may not be fully realized - depending on each respective parameter's trace-fully-realized setting.
|
|
||||||
///
|
|
||||||
/// The purpose is so the end user can understand how their expression expanded at run time. For example, consider
|
|
||||||
/// the expression: eq(variables.publish, 'true'). The runtime-expanded expression may be: eq('true', 'true')
|
|
||||||
/// </summary>
|
|
||||||
protected override Boolean TraceFullyRealized => true;
|
|
||||||
|
|
||||||
internal sealed override String ConvertToExpression()
|
|
||||||
{
|
|
||||||
return String.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0}({1})",
|
|
||||||
Name,
|
|
||||||
String.Join(", ", Parameters.Select(x => x.ConvertToExpression())));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
|
|
||||||
{
|
|
||||||
// Check if the result was stored
|
|
||||||
if (context.TryGetTraceResult(this, out String result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0}({1})",
|
|
||||||
Name,
|
|
||||||
String.Join(", ", Parameters.Select(x => x.ConvertToRealizedExpression(context))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class GreaterThanNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class GreaterThanOrEqualNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) >= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IBoolean
|
|
||||||
{
|
|
||||||
Boolean GetBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IExpressionNode
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the expression and attempts to cast or deserialize the result to the specified
|
|
||||||
/// type. The specified type can either be simple type or a JSON-serializable class. Allowed
|
|
||||||
/// simple types are: Boolean, String, Version, Byte, SByte, Int16, UInt16, Int32, UInt32,
|
|
||||||
/// Int64, UInt64, Single, Double, or Decimal. When a JSON-serializable class is specified, the
|
|
||||||
/// following rules are applied: If the type of the evaluation result object, is assignable to
|
|
||||||
/// the specified type, then the result will be cast and returned. If the evaluation result
|
|
||||||
/// object is a String, it will be deserialized as the specified type. If the evaluation result
|
|
||||||
/// object is null, null will be returned.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trace">Optional trace writer</param>
|
|
||||||
/// <param name="secretMasker">Optional secret masker</param>
|
|
||||||
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
|
|
||||||
T Evaluate<T>(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the expression and returns the result.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trace">Optional trace writer</param>
|
|
||||||
/// <param name="secretMasker">Optional secret masker</param>
|
|
||||||
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
|
|
||||||
Object Evaluate(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the expression and casts the result to a Boolean.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trace">Optional trace writer</param>
|
|
||||||
/// <param name="secretMasker">Optional secret masker</param>
|
|
||||||
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
|
|
||||||
Boolean EvaluateBoolean(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state);
|
|
||||||
|
|
||||||
IEnumerable<T> GetParameters<T>() where T : IExpressionNode;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the expression and returns the result, wrapped in the SDK helper
|
|
||||||
/// for converting, comparing, and traversing objects.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trace">Optional trace writer</param>
|
|
||||||
/// <param name="secretMasker">Optional secret masker</param>
|
|
||||||
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
|
|
||||||
/// <param name="options">Evaluation options</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
EvaluationResult EvaluateResult(
|
|
||||||
ITraceWriter trace,
|
|
||||||
ISecretMasker secretMasker,
|
|
||||||
Object state,
|
|
||||||
EvaluationOptions options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public interface IFunctionInfo
|
|
||||||
{
|
|
||||||
String Name { get; }
|
|
||||||
Int32 MinParameters { get; }
|
|
||||||
Int32 MaxParameters { get; }
|
|
||||||
FunctionNode CreateNode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public interface INamedValueInfo
|
|
||||||
{
|
|
||||||
String Name { get; }
|
|
||||||
NamedValueNode CreateNode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface INumber
|
|
||||||
{
|
|
||||||
Decimal GetNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IReadOnlyArray : IReadOnlyList<Object>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IReadOnlyObject : IReadOnlyDictionary<String, Object>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IString
|
|
||||||
{
|
|
||||||
String GetString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public interface ITraceWriter
|
|
||||||
{
|
|
||||||
void Info(String message);
|
|
||||||
void Verbose(String message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class InNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
EvaluationResult left = Parameters[0].Evaluate(context);
|
|
||||||
for (Int32 i = 1; i < Parameters.Count; i++)
|
|
||||||
{
|
|
||||||
EvaluationResult right = Parameters[i].Evaluate(context);
|
|
||||||
if (left.Equals(context, right))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,452 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class IndexerNode : ContainerNode
|
|
||||||
{
|
|
||||||
internal IndexerNode()
|
|
||||||
{
|
|
||||||
Name = "indexer";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override Boolean TraceFullyRealized => true;
|
|
||||||
|
|
||||||
internal sealed override String ConvertToExpression()
|
|
||||||
{
|
|
||||||
return String.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0}[{1}]",
|
|
||||||
Parameters[0].ConvertToExpression(),
|
|
||||||
Parameters[1].ConvertToExpression());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
|
|
||||||
{
|
|
||||||
// Check if the result was stored
|
|
||||||
if (context.TryGetTraceResult(this, out String result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConvertToExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(
|
|
||||||
EvaluationContext context,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
EvaluationResult firstParameter = Parameters[0].Evaluate(context);
|
|
||||||
|
|
||||||
if (context.Options.UseCollectionInterfaces)
|
|
||||||
{
|
|
||||||
if (!firstParameter.TryGetCollectionInterface(out Object collection))
|
|
||||||
{
|
|
||||||
// Even if we can't get the collection interface, return empty filtered array if it is a wildcard.
|
|
||||||
if (Parameters.Count > 2)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return new FilteredArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle operating on a filtered array
|
|
||||||
if (collection is FilteredArray filteredArray)
|
|
||||||
{
|
|
||||||
return HandleFilteredArray(context, filteredArray, out resultMemory);
|
|
||||||
}
|
|
||||||
// Handle operating on an object
|
|
||||||
else if (collection is IReadOnlyObject obj)
|
|
||||||
{
|
|
||||||
return HandleObject(context, obj, out resultMemory);
|
|
||||||
}
|
|
||||||
// Handle operating on an array
|
|
||||||
else if (collection is IReadOnlyArray array)
|
|
||||||
{
|
|
||||||
return HandleArray(context, array, out resultMemory);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Object result = null;
|
|
||||||
|
|
||||||
if (firstParameter.Kind == ValueKind.Array && firstParameter.Value is JArray)
|
|
||||||
{
|
|
||||||
var jarray = firstParameter.Value as JArray;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
if (index.Kind == ValueKind.Number)
|
|
||||||
{
|
|
||||||
Decimal d = (Decimal)index.Value;
|
|
||||||
if (d >= 0m && d < (Decimal)jarray.Count && d == Math.Floor(d))
|
|
||||||
{
|
|
||||||
result = jarray[(Int32)d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (index.Kind == ValueKind.String && !String.IsNullOrEmpty(index.Value as String))
|
|
||||||
{
|
|
||||||
Decimal d;
|
|
||||||
if (index.TryConvertToNumber(context, out d))
|
|
||||||
{
|
|
||||||
if (d >= 0m && d < (Decimal)jarray.Count && d == Math.Floor(d))
|
|
||||||
{
|
|
||||||
result = jarray[(Int32)d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (firstParameter.Kind == ValueKind.Object)
|
|
||||||
{
|
|
||||||
if (firstParameter.Value is JObject)
|
|
||||||
{
|
|
||||||
var jobject = firstParameter.Value as JObject;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
String s;
|
|
||||||
if (index.TryConvertToString(context, out s))
|
|
||||||
{
|
|
||||||
result = jobject[s];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (firstParameter.Value is IDictionary<String, String>)
|
|
||||||
{
|
|
||||||
var dictionary = firstParameter.Value as IDictionary<String, String>;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
if (index.TryConvertToString(context, out String key))
|
|
||||||
{
|
|
||||||
if (!dictionary.TryGetValue(key, out String resultString))
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = resultString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (firstParameter.Value is IDictionary<String, Object>)
|
|
||||||
{
|
|
||||||
var dictionary = firstParameter.Value as IDictionary<String, Object>;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
String s;
|
|
||||||
if (index.TryConvertToString(context, out s))
|
|
||||||
{
|
|
||||||
if (!dictionary.TryGetValue(s, out result))
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (firstParameter.Value is IReadOnlyDictionary<String, String>)
|
|
||||||
{
|
|
||||||
var dictionary = firstParameter.Value as IReadOnlyDictionary<String, String>;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
if (index.TryConvertToString(context, out String key))
|
|
||||||
{
|
|
||||||
if (!dictionary.TryGetValue(key, out String resultString))
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = resultString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (firstParameter.Value is IReadOnlyDictionary<String, Object>)
|
|
||||||
{
|
|
||||||
var dictionary = firstParameter.Value as IReadOnlyDictionary<String, Object>;
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
String s;
|
|
||||||
if (index.TryConvertToString(context, out s))
|
|
||||||
{
|
|
||||||
if (!dictionary.TryGetValue(s, out result))
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var contract = s_serializer.Value.ContractResolver.ResolveContract(firstParameter.Value.GetType());
|
|
||||||
var objectContract = contract as JsonObjectContract;
|
|
||||||
if (objectContract != null)
|
|
||||||
{
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
if (index.TryConvertToString(context, out String key))
|
|
||||||
{
|
|
||||||
var property = objectContract.Properties.GetClosestMatchProperty(key);
|
|
||||||
if (property != null)
|
|
||||||
{
|
|
||||||
result = objectContract.Properties[property.PropertyName].ValueProvider.GetValue(firstParameter.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dictionaryContract = contract as JsonDictionaryContract;
|
|
||||||
if (dictionaryContract != null && dictionaryContract.DictionaryKeyType == typeof(String))
|
|
||||||
{
|
|
||||||
EvaluationResult index = Parameters[1].Evaluate(context);
|
|
||||||
if (index.TryConvertToString(context, out String key))
|
|
||||||
{
|
|
||||||
var genericMethod = s_tryGetValueTemplate.Value.MakeGenericMethod(dictionaryContract.DictionaryValueType);
|
|
||||||
resultMemory = null;
|
|
||||||
return genericMethod.Invoke(null, new[] { firstParameter.Value, key });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = null;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object HandleFilteredArray(
|
|
||||||
EvaluationContext context,
|
|
||||||
FilteredArray filteredArray,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
EvaluationResult indexResult = Parameters[1].Evaluate(context);
|
|
||||||
var indexHelper = new IndexHelper(indexResult, context);
|
|
||||||
|
|
||||||
Boolean isFilter;
|
|
||||||
if (Parameters.Count > 2)
|
|
||||||
{
|
|
||||||
isFilter = true;
|
|
||||||
|
|
||||||
if (!String.Equals(indexHelper.StringIndex, ExpressionConstants.Wildcard.ToString(), StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected filter '{indexHelper.StringIndex}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
isFilter = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new FilteredArray();
|
|
||||||
var counter = new MemoryCounter(this, context.Options.MaxMemory);
|
|
||||||
|
|
||||||
foreach (var item in filteredArray)
|
|
||||||
{
|
|
||||||
// Leverage the expression SDK to traverse the object
|
|
||||||
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
|
|
||||||
if (itemResult.TryGetCollectionInterface(out Object nestedCollection))
|
|
||||||
{
|
|
||||||
// Apply the index to a child object
|
|
||||||
if (nestedCollection is IReadOnlyObject nestedObject)
|
|
||||||
{
|
|
||||||
if (isFilter)
|
|
||||||
{
|
|
||||||
foreach (var val in nestedObject.Values)
|
|
||||||
{
|
|
||||||
result.Add(val);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (indexHelper.HasStringIndex)
|
|
||||||
{
|
|
||||||
if (nestedObject.TryGetValue(indexHelper.StringIndex, out Object nestedObjectValue))
|
|
||||||
{
|
|
||||||
result.Add(nestedObjectValue);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply the index to a child array
|
|
||||||
else if (nestedCollection is IReadOnlyArray nestedArray)
|
|
||||||
{
|
|
||||||
if (isFilter)
|
|
||||||
{
|
|
||||||
foreach (var val in nestedArray)
|
|
||||||
{
|
|
||||||
result.Add(val);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (indexHelper.HasIntegerIndex &&
|
|
||||||
indexHelper.IntegerIndex < nestedArray.Count)
|
|
||||||
{
|
|
||||||
result.Add(nestedArray[indexHelper.IntegerIndex]);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object HandleObject(
|
|
||||||
EvaluationContext context,
|
|
||||||
IReadOnlyObject obj,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
EvaluationResult indexResult = Parameters[1].Evaluate(context);
|
|
||||||
var indexHelper = new IndexHelper(indexResult, context);
|
|
||||||
|
|
||||||
if (indexHelper.HasStringIndex)
|
|
||||||
{
|
|
||||||
Boolean isFilter = Parameters.Count > 2;
|
|
||||||
|
|
||||||
if (isFilter)
|
|
||||||
{
|
|
||||||
var filteredArray = new FilteredArray();
|
|
||||||
var counter = new MemoryCounter(this, context.Options.MaxMemory);
|
|
||||||
|
|
||||||
foreach (var val in obj.Values)
|
|
||||||
{
|
|
||||||
filteredArray.Add(val);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
|
|
||||||
return filteredArray;
|
|
||||||
}
|
|
||||||
else if (obj.TryGetValue(indexHelper.StringIndex, out Object result))
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object HandleArray(
|
|
||||||
EvaluationContext context,
|
|
||||||
IReadOnlyArray array,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
// Similar to as above but for an array
|
|
||||||
EvaluationResult indexResult = Parameters[1].Evaluate(context);
|
|
||||||
var indexHelper = new IndexHelper(indexResult, context);
|
|
||||||
|
|
||||||
// When we are operating on a array and it has three parameters, with the second being a string * and the third being a true boolean, it's a filtered array.
|
|
||||||
if (Parameters.Count > 2)
|
|
||||||
{
|
|
||||||
var filtered = new FilteredArray();
|
|
||||||
var counter = new MemoryCounter(this, context.Options.MaxMemory);
|
|
||||||
|
|
||||||
foreach (var x in array)
|
|
||||||
{
|
|
||||||
filtered.Add(x);
|
|
||||||
counter.Add(IntPtr.Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexHelper.HasIntegerIndex && indexHelper.IntegerIndex < array.Count)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return array[indexHelper.IntegerIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMemory = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: remove with feature flag cleanup for "UseCollectionInterfaces"
|
|
||||||
private static Object TryGetValue<TValue>(
|
|
||||||
IDictionary<String, TValue> dictionary,
|
|
||||||
String key)
|
|
||||||
{
|
|
||||||
TValue value;
|
|
||||||
if (!dictionary.TryGetValue(key, out value))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FilteredArray : IReadOnlyArray
|
|
||||||
{
|
|
||||||
public FilteredArray()
|
|
||||||
{
|
|
||||||
m_list = new List<Object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Object o)
|
|
||||||
{
|
|
||||||
m_list.Add(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Count => m_list.Count;
|
|
||||||
|
|
||||||
public Object this[Int32 index] => m_list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Object> GetEnumerator() => m_list.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => m_list.GetEnumerator();
|
|
||||||
|
|
||||||
private readonly IList<Object> m_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IndexHelper
|
|
||||||
{
|
|
||||||
public Boolean HasIntegerIndex => m_integerIndex.Value.Item1;
|
|
||||||
public Int32 IntegerIndex => m_integerIndex.Value.Item2;
|
|
||||||
|
|
||||||
public Boolean HasStringIndex => m_stringIndex.Value.Item1;
|
|
||||||
public String StringIndex => m_stringIndex.Value.Item2;
|
|
||||||
|
|
||||||
public IndexHelper(
|
|
||||||
EvaluationResult result,
|
|
||||||
EvaluationContext context)
|
|
||||||
{
|
|
||||||
m_result = result;
|
|
||||||
m_context = context;
|
|
||||||
|
|
||||||
m_integerIndex = new Lazy<Tuple<Boolean, Int32>>(() =>
|
|
||||||
{
|
|
||||||
if (m_result.TryConvertToNumber(m_context, out Decimal decimalIndex) &&
|
|
||||||
decimalIndex >= 0m)
|
|
||||||
{
|
|
||||||
return new Tuple<Boolean, Int32>(true, (Int32)Math.Floor(decimalIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<Boolean, Int32>(false, default(Int32));
|
|
||||||
});
|
|
||||||
|
|
||||||
m_stringIndex = new Lazy<Tuple<Boolean, String>>(() =>
|
|
||||||
{
|
|
||||||
if (m_result.TryConvertToString(m_context, out String stringIndex))
|
|
||||||
{
|
|
||||||
return new Tuple<Boolean, String>(true, stringIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<Boolean, String>(false, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Lazy<Tuple<Boolean, Int32>> m_integerIndex;
|
|
||||||
private Lazy<Tuple<Boolean, String>> m_stringIndex;
|
|
||||||
|
|
||||||
private readonly EvaluationResult m_result;
|
|
||||||
private readonly EvaluationContext m_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: remove these properties with feature flag cleanup for "UseCollectionInterfaces"
|
|
||||||
private static Lazy<JsonSerializer> s_serializer = new Lazy<JsonSerializer>(() => JsonUtility.CreateJsonSerializer());
|
|
||||||
private static Lazy<MethodInfo> s_tryGetValueTemplate = new Lazy<MethodInfo>(() => typeof(IndexerNode).GetTypeInfo().GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class JoinNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => true;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
var items = Parameters[1].Evaluate(context);
|
|
||||||
|
|
||||||
if (items.TryGetCollectionInterface(out var collection) && collection is IReadOnlyArray array)
|
|
||||||
{
|
|
||||||
if (array.Count > 0)
|
|
||||||
{
|
|
||||||
var result = new StringBuilder();
|
|
||||||
var memory = new MemoryCounter(this, context.Options.MaxMemory);
|
|
||||||
|
|
||||||
// Append the first item
|
|
||||||
var item = array[0];
|
|
||||||
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
|
|
||||||
if (itemResult.TryConvertToString(context, out String itemString))
|
|
||||||
{
|
|
||||||
memory.Add(itemString);
|
|
||||||
result.Append(itemString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// More items?
|
|
||||||
if (array.Count > 1)
|
|
||||||
{
|
|
||||||
var separator = Parameters[0].EvaluateString(context);
|
|
||||||
|
|
||||||
for (var i = 1; i < array.Count; i++)
|
|
||||||
{
|
|
||||||
// Append the separator
|
|
||||||
memory.Add(separator);
|
|
||||||
result.Append(separator);
|
|
||||||
|
|
||||||
// Append the next item
|
|
||||||
var nextItem = array[i];
|
|
||||||
var nextItemResult = EvaluationResult.CreateIntermediateResult(context, nextItem, out _);
|
|
||||||
if (nextItemResult.TryConvertToString(context, out String nextItemString))
|
|
||||||
{
|
|
||||||
memory.Add(nextItemString);
|
|
||||||
result.Append(nextItemString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return String.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (items.TryConvertToString(context, out String str))
|
|
||||||
{
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return String.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class LessThanNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) < 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class LessThanOrEqualNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) <= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class LexicalAnalyzer
|
|
||||||
{
|
|
||||||
public LexicalAnalyzer(String expression, IEnumerable<String> namedValues, IEnumerable<String> functions, Boolean allowKeywordHyphens)
|
|
||||||
{
|
|
||||||
m_expression = expression;
|
|
||||||
m_extensionNamedValues = new HashSet<String>(namedValues ?? new String[0], StringComparer.OrdinalIgnoreCase);
|
|
||||||
m_extensionFunctions = new HashSet<String>(functions ?? new String[0], StringComparer.OrdinalIgnoreCase);
|
|
||||||
m_allowKeyHyphens = allowKeywordHyphens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryGetNextToken(ref Token token)
|
|
||||||
{
|
|
||||||
// Skip whitespace.
|
|
||||||
while (m_index < m_expression.Length && Char.IsWhiteSpace(m_expression[m_index]))
|
|
||||||
{
|
|
||||||
m_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test end of string.
|
|
||||||
if (m_index >= m_expression.Length)
|
|
||||||
{
|
|
||||||
token = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the first character to determine the type of token.
|
|
||||||
Char c = m_expression[m_index];
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case ExpressionConstants.StartIndex:
|
|
||||||
token = new Token(TokenKind.StartIndex, c, m_index++);
|
|
||||||
break;
|
|
||||||
case ExpressionConstants.StartParameter:
|
|
||||||
token = new Token(TokenKind.StartParameter, c, m_index++);
|
|
||||||
break;
|
|
||||||
case ExpressionConstants.EndIndex:
|
|
||||||
token = new Token(TokenKind.EndIndex, c, m_index++);
|
|
||||||
break;
|
|
||||||
case ExpressionConstants.EndParameter:
|
|
||||||
token = new Token(TokenKind.EndParameter, c, m_index++);
|
|
||||||
break;
|
|
||||||
case ExpressionConstants.Separator:
|
|
||||||
token = new Token(TokenKind.Separator, c, m_index++);
|
|
||||||
break;
|
|
||||||
case ExpressionConstants.Wildcard:
|
|
||||||
token = new Token(TokenKind.Wildcard, c, m_index++);
|
|
||||||
break;
|
|
||||||
case '\'':
|
|
||||||
token = ReadStringToken();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (c == '.')
|
|
||||||
{
|
|
||||||
if (m_lastToken == null ||
|
|
||||||
m_lastToken.Kind == TokenKind.Separator ||
|
|
||||||
m_lastToken.Kind == TokenKind.StartIndex ||
|
|
||||||
m_lastToken.Kind == TokenKind.StartParameter)
|
|
||||||
{
|
|
||||||
token = ReadNumberOrVersionToken();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token = new Token(TokenKind.Dereference, c, m_index++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == '-' || (c >= '0' && c <= '9'))
|
|
||||||
{
|
|
||||||
token = ReadNumberOrVersionToken();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token = ReadKeywordToken(m_allowKeyHyphens);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_lastToken = token;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean TryPeekNextToken(ref Token token)
|
|
||||||
{
|
|
||||||
// Record the state.
|
|
||||||
Int32 index = m_index;
|
|
||||||
Token lastToken = m_lastToken;
|
|
||||||
|
|
||||||
// Get next token.
|
|
||||||
Boolean result = TryGetNextToken(ref token);
|
|
||||||
|
|
||||||
// Restore the state.
|
|
||||||
m_index = index;
|
|
||||||
m_lastToken = lastToken;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ReadNumberOrVersionToken()
|
|
||||||
{
|
|
||||||
Int32 startIndex = m_index;
|
|
||||||
Int32 periods = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (m_expression[m_index] == '.')
|
|
||||||
{
|
|
||||||
periods++;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_index++;
|
|
||||||
}
|
|
||||||
while (m_index < m_expression.Length && (!TestWhitespaceOrPunctuation(m_expression[m_index]) || m_expression[m_index] == '.'));
|
|
||||||
|
|
||||||
Int32 length = m_index - startIndex;
|
|
||||||
String str = m_expression.Substring(startIndex, length);
|
|
||||||
if (periods >= 2)
|
|
||||||
{
|
|
||||||
Version version;
|
|
||||||
if (Version.TryParse(str, out version))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.Version, str, startIndex, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Note, NumberStyles.AllowThousands cannot be allowed since comma has special meaning as a token separator.
|
|
||||||
Decimal d;
|
|
||||||
if (Decimal.TryParse(
|
|
||||||
str,
|
|
||||||
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign,
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
out d))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.Number, str, startIndex, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Token(TokenKind.Unrecognized, str, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ReadKeywordToken(bool allowHyphen)
|
|
||||||
{
|
|
||||||
// Read to the end of the keyword.
|
|
||||||
Int32 startIndex = m_index;
|
|
||||||
m_index++; // Skip the first char. It is already known to be the start of the keyword.
|
|
||||||
while (m_index < m_expression.Length && !TestWhitespaceOrPunctuation(m_expression[m_index]))
|
|
||||||
{
|
|
||||||
m_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if valid keyword character sequence.
|
|
||||||
Int32 length = m_index - startIndex;
|
|
||||||
String str = m_expression.Substring(startIndex, length);
|
|
||||||
if (TestKeyword(str, allowHyphen))
|
|
||||||
{
|
|
||||||
// Test if follows property dereference operator.
|
|
||||||
if (m_lastToken != null && m_lastToken.Kind == TokenKind.Dereference)
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.PropertyName, str, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean
|
|
||||||
if (str.Equals(Boolean.TrueString, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.Boolean, str, startIndex, true);
|
|
||||||
}
|
|
||||||
else if (str.Equals(Boolean.FalseString, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.Boolean, str, startIndex, false);
|
|
||||||
}
|
|
||||||
// Well-known function
|
|
||||||
else if (ExpressionConstants.WellKnownFunctions.ContainsKey(str))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.WellKnownFunction, str, startIndex);
|
|
||||||
}
|
|
||||||
// Extension value
|
|
||||||
else if (m_extensionNamedValues.Contains(str))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.ExtensionNamedValue, str, startIndex);
|
|
||||||
}
|
|
||||||
// Extension function
|
|
||||||
else if (m_extensionFunctions.Contains(str))
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.ExtensionFunction, str, startIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown keyword
|
|
||||||
return new Token(TokenKind.UnknownKeyword, str, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ReadStringToken()
|
|
||||||
{
|
|
||||||
Int32 startIndex = m_index;
|
|
||||||
Char c;
|
|
||||||
Boolean closed = false;
|
|
||||||
var str = new StringBuilder();
|
|
||||||
m_index++; // Skip the leading single-quote.
|
|
||||||
while (m_index < m_expression.Length)
|
|
||||||
{
|
|
||||||
c = m_expression[m_index++];
|
|
||||||
if (c == '\'')
|
|
||||||
{
|
|
||||||
// End of string.
|
|
||||||
if (m_index >= m_expression.Length || m_expression[m_index] != '\'')
|
|
||||||
{
|
|
||||||
closed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escaped single quote.
|
|
||||||
m_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
str.Append(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
Int32 length = m_index - startIndex;
|
|
||||||
String rawValue = m_expression.Substring(startIndex, length);
|
|
||||||
if (closed)
|
|
||||||
{
|
|
||||||
return new Token(TokenKind.String, rawValue, startIndex, str.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Token(TokenKind.Unrecognized, rawValue, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean TestKeyword(String str, bool allowHyphen)
|
|
||||||
{
|
|
||||||
if (String.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Char first = str[0];
|
|
||||||
if ((first >= 'a' && first <= 'z') ||
|
|
||||||
(first >= 'A' && first <= 'Z') ||
|
|
||||||
first == '_')
|
|
||||||
{
|
|
||||||
for (Int32 i = 1 ; i < str.Length ; i++)
|
|
||||||
{
|
|
||||||
Char c = str[i];
|
|
||||||
if ((c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= 'A' && c <= 'Z') ||
|
|
||||||
(c >= '0' && c <= '9') ||
|
|
||||||
c == '_' || (allowHyphen && c == '-'))
|
|
||||||
{
|
|
||||||
// OK
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean TestWhitespaceOrPunctuation(Char c)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case ExpressionConstants.StartIndex:
|
|
||||||
case ExpressionConstants.StartParameter:
|
|
||||||
case ExpressionConstants.EndIndex:
|
|
||||||
case ExpressionConstants.EndParameter:
|
|
||||||
case ExpressionConstants.Separator:
|
|
||||||
case ExpressionConstants.Dereference:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return char.IsWhiteSpace(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly String m_expression; // Raw expression string.
|
|
||||||
private readonly HashSet<String> m_extensionFunctions;
|
|
||||||
private readonly HashSet<String> m_extensionNamedValues;
|
|
||||||
private Int32 m_index; // Index of raw condition string.
|
|
||||||
private Token m_lastToken;
|
|
||||||
private readonly Boolean m_allowKeyHyphens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class LiteralValueNode : ExpressionNode
|
|
||||||
{
|
|
||||||
public LiteralValueNode(Object val)
|
|
||||||
{
|
|
||||||
ValueKind kind;
|
|
||||||
|
|
||||||
// Note, it is OK to pass null EvaluationOptions here since the parser does not support
|
|
||||||
// localized values. For example, if parsing local date-times were supported, then we
|
|
||||||
// would need to know the account's time zone at parse time. This is an OK limitation,
|
|
||||||
// since we can defer this type of problem to runtime, for example by adding a parseDate function.
|
|
||||||
Value = ExpressionUtil.ConvertToCanonicalValue(null, val, out kind, out _, out _);
|
|
||||||
|
|
||||||
Kind = kind;
|
|
||||||
Name = kind.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueKind Kind { get; }
|
|
||||||
|
|
||||||
public Object Value { get; }
|
|
||||||
|
|
||||||
// Prevent the value from being stored on the evaluation context.
|
|
||||||
// This avoids unneccessarily duplicating the value in memory.
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
internal sealed override String ConvertToExpression()
|
|
||||||
{
|
|
||||||
return ExpressionUtil.FormatValue(null, Value, Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return ExpressionUtil.FormatValue(null, Value, Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper class for ExpressionNode authors. This class helps calculate memory overhead for a result object.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class MemoryCounter
|
|
||||||
{
|
|
||||||
internal MemoryCounter(
|
|
||||||
ExpressionNode node,
|
|
||||||
Int32? maxBytes)
|
|
||||||
{
|
|
||||||
m_node = node;
|
|
||||||
m_maxBytes = (maxBytes ?? 0) > 0 ? maxBytes.Value : Int32.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 CurrentBytes => m_currentBytes;
|
|
||||||
|
|
||||||
public void Add(Int32 amount)
|
|
||||||
{
|
|
||||||
if (!TryAdd(amount))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(ExpressionResources.ExceededAllowedMemory(m_node?.ConvertToExpression()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(String value)
|
|
||||||
{
|
|
||||||
Add(CalculateSize(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(
|
|
||||||
JToken value,
|
|
||||||
Boolean traverse)
|
|
||||||
{
|
|
||||||
// This measurement doesn't have to be perfect
|
|
||||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
|
||||||
|
|
||||||
if (value is null)
|
|
||||||
{
|
|
||||||
Add(MinObjectSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!traverse)
|
|
||||||
{
|
|
||||||
switch (value.Type)
|
|
||||||
{
|
|
||||||
case JTokenType.Bytes:
|
|
||||||
case JTokenType.String:
|
|
||||||
case JTokenType.Uri:
|
|
||||||
Add(value.ToObject<String>());
|
|
||||||
return;
|
|
||||||
|
|
||||||
case JTokenType.Property:
|
|
||||||
var property = value as JProperty;
|
|
||||||
Add(property.Name);
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Add(MinObjectSize);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// Descend as much as possible
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Add bytes
|
|
||||||
Add(value, false);
|
|
||||||
|
|
||||||
// Descend
|
|
||||||
if (value.HasValues)
|
|
||||||
{
|
|
||||||
value = value.First;
|
|
||||||
}
|
|
||||||
// No more descendants
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next sibling or ancestor sibling
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var sibling = value.Next;
|
|
||||||
|
|
||||||
// Sibling found
|
|
||||||
if (sibling != null)
|
|
||||||
{
|
|
||||||
value = sibling;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ascend
|
|
||||||
value = value.Parent;
|
|
||||||
|
|
||||||
} while (value != null);
|
|
||||||
|
|
||||||
} while (value != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddMinObjectSize()
|
|
||||||
{
|
|
||||||
Add(MinObjectSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(String value)
|
|
||||||
{
|
|
||||||
m_currentBytes -= CalculateSize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Int32 CalculateSize(String value)
|
|
||||||
{
|
|
||||||
// This measurement doesn't have to be perfect.
|
|
||||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
|
||||||
|
|
||||||
Int32 bytes;
|
|
||||||
checked
|
|
||||||
{
|
|
||||||
bytes = StringBaseOverhead + ((value?.Length ?? 0) * 2);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Boolean TryAdd(Int32 amount)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
checked
|
|
||||||
{
|
|
||||||
amount += m_currentBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > m_maxBytes)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_currentBytes = amount;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (OverflowException)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Boolean TryAdd(String value)
|
|
||||||
{
|
|
||||||
return TryAdd(CalculateSize(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal const Int32 MinObjectSize = 24;
|
|
||||||
internal const Int32 StringBaseOverhead = 26;
|
|
||||||
private readonly Int32 m_maxBytes;
|
|
||||||
private readonly ExpressionNode m_node;
|
|
||||||
private Int32 m_currentBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public class NamedValueInfo<T> : INamedValueInfo
|
|
||||||
where T : NamedValueNode, new()
|
|
||||||
{
|
|
||||||
public NamedValueInfo(String name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String Name { get; }
|
|
||||||
|
|
||||||
public NamedValueNode CreateNode()
|
|
||||||
{
|
|
||||||
return new T();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public abstract class NamedValueNode : ExpressionNode
|
|
||||||
{
|
|
||||||
internal sealed override string ConvertToExpression() => Name;
|
|
||||||
|
|
||||||
protected sealed override Boolean TraceFullyRealized => true;
|
|
||||||
|
|
||||||
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
|
|
||||||
{
|
|
||||||
// Check if the result was stored
|
|
||||||
if (context.TryGetTraceResult(this, out String result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class NotEqualNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return !Parameters[0].Evaluate(context).Equals(context, Parameters[1].Evaluate(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class NotInNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
EvaluationResult left = Parameters[0].Evaluate(context);
|
|
||||||
for (Int32 i = 1; i < Parameters.Count; i++)
|
|
||||||
{
|
|
||||||
EvaluationResult right = Parameters[i].Evaluate(context);
|
|
||||||
if (left.Equals(context, right))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class NotNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return !Parameters[0].EvaluateBoolean(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class OrNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
foreach (ExpressionNode parameter in Parameters)
|
|
||||||
{
|
|
||||||
if (parameter.EvaluateBoolean(context))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
public sealed class ParseException : ExpressionException
|
|
||||||
{
|
|
||||||
internal ParseException(ParseExceptionKind kind, Token token, String expression)
|
|
||||||
: base(secretMasker: null, message: String.Empty)
|
|
||||||
{
|
|
||||||
Expression = expression;
|
|
||||||
Kind = kind;
|
|
||||||
RawToken = token?.RawValue;
|
|
||||||
TokenIndex = token?.Index ?? 0;
|
|
||||||
String description;
|
|
||||||
switch (kind)
|
|
||||||
{
|
|
||||||
case ParseExceptionKind.ExceededMaxDepth:
|
|
||||||
description = ExpressionResources.ExceededMaxExpressionDepth(ExpressionConstants.MaxDepth);
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.ExceededMaxLength:
|
|
||||||
description = ExpressionResources.ExceededMaxExpressionLength(ExpressionConstants.MaxLength);
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.ExpectedPropertyName:
|
|
||||||
description = ExpressionResources.ExpectedPropertyName();
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.ExpectedStartParameter:
|
|
||||||
description = ExpressionResources.ExpectedStartParameter();
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.UnclosedFunction:
|
|
||||||
description = ExpressionResources.UnclosedFunction();
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.UnclosedIndexer:
|
|
||||||
description = ExpressionResources.UnclosedIndexer();
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.UnexpectedSymbol:
|
|
||||||
description = ExpressionResources.UnexpectedSymbol();
|
|
||||||
break;
|
|
||||||
case ParseExceptionKind.UnrecognizedValue:
|
|
||||||
description = ExpressionResources.UnrecognizedValue();
|
|
||||||
break;
|
|
||||||
default: // Should never reach here.
|
|
||||||
throw new Exception($"Unexpected parse exception kind '{kind}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token == null)
|
|
||||||
{
|
|
||||||
Message = ExpressionResources.ParseErrorWithFwlink(description);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Message = ExpressionResources.ParseErrorWithTokenInfo(description, RawToken, TokenIndex + 1, Expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal String Expression { get; }
|
|
||||||
|
|
||||||
internal ParseExceptionKind Kind { get; }
|
|
||||||
|
|
||||||
internal String RawToken { get; }
|
|
||||||
|
|
||||||
internal Int32 TokenIndex { get; }
|
|
||||||
|
|
||||||
public sealed override String Message { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal enum ParseExceptionKind
|
|
||||||
{
|
|
||||||
ExceededMaxDepth,
|
|
||||||
ExceededMaxLength,
|
|
||||||
ExpectedPropertyName,
|
|
||||||
ExpectedStartParameter,
|
|
||||||
UnclosedFunction,
|
|
||||||
UnclosedIndexer,
|
|
||||||
UnexpectedSymbol,
|
|
||||||
UnrecognizedValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class ResultMemory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Only set a non-null value when both of the following conditions are met:
|
|
||||||
/// 1) The result is a complex object. In other words, the result is
|
|
||||||
/// not a simple type: string, boolean, number, version, datetime, or null.
|
|
||||||
/// 2) The result is a newly created object.
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// For example, consider a function jsonParse() which takes a string parameter,
|
|
||||||
/// and returns a JToken object. The JToken object is newly created and a rough
|
|
||||||
/// measurement should be returned for the number of bytes it consumes in memory.
|
|
||||||
/// </para>
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// For another example, consider a function which returns a sub-object from a
|
|
||||||
/// complex parameter value. From the perspective of an individual function,
|
|
||||||
/// the size of the complex parameter value is unknown. In this situation, set the
|
|
||||||
/// value to IntPtr.Size.
|
|
||||||
/// </para>
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// When you are unsure, set the value to null. Null indicates the overhead of a
|
|
||||||
/// new pointer should be accounted for.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public Int32? Bytes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether <c ref="Bytes" /> represents the total size of the result.
|
|
||||||
/// True indicates the accounting-overhead of downstream parameters can be discarded.
|
|
||||||
///
|
|
||||||
/// For <c ref="EvaluationOptions.Converters" />, this value is currently ignored.
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// For example, consider a funciton jsonParse() which takes a string paramter,
|
|
||||||
/// and returns a JToken object. The JToken object is newly created and a rough
|
|
||||||
/// measurement should be returned for the amount of bytes it consumes in memory.
|
|
||||||
/// Set the <c ref="IsTotal" /> to true, since new object contains no references
|
|
||||||
/// to previously allocated memory.
|
|
||||||
/// </para>
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// For another example, consider a function which wraps a complex parameter result.
|
|
||||||
/// <c ref="Bytes" /> should be set to the amount of newly allocated memory.
|
|
||||||
/// However since the object references previously allocated memory, set <c ref="IsTotal" />
|
|
||||||
/// to false.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public Boolean IsTotal { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class StartsWithNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
String left = Parameters[0].EvaluateString(context) ?? String.Empty;
|
|
||||||
String right = Parameters[1].EvaluateString(context) ?? String.Empty;
|
|
||||||
return left.StartsWith(right, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class Token
|
|
||||||
{
|
|
||||||
public Token(TokenKind kind, Char rawValue, Int32 index, Object parsedValue = null)
|
|
||||||
: this(kind, rawValue.ToString(), index, parsedValue)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Token(TokenKind kind, String rawValue, Int32 index, Object parsedValue = null)
|
|
||||||
{
|
|
||||||
Kind = kind;
|
|
||||||
RawValue = rawValue;
|
|
||||||
Index = index;
|
|
||||||
ParsedValue = parsedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TokenKind Kind { get; }
|
|
||||||
|
|
||||||
public String RawValue { get; }
|
|
||||||
|
|
||||||
public Int32 Index { get; }
|
|
||||||
|
|
||||||
public Object ParsedValue { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal enum TokenKind
|
|
||||||
{
|
|
||||||
// Punctuation
|
|
||||||
StartIndex,
|
|
||||||
StartParameter,
|
|
||||||
EndIndex,
|
|
||||||
EndParameter,
|
|
||||||
Separator,
|
|
||||||
Dereference,
|
|
||||||
Wildcard,
|
|
||||||
|
|
||||||
// Values
|
|
||||||
Boolean,
|
|
||||||
Number,
|
|
||||||
Version,
|
|
||||||
String,
|
|
||||||
PropertyName,
|
|
||||||
|
|
||||||
// Functions and named-values
|
|
||||||
WellKnownFunction,
|
|
||||||
ExtensionFunction,
|
|
||||||
ExtensionNamedValue,
|
|
||||||
UnknownKeyword,
|
|
||||||
|
|
||||||
Unrecognized,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
using System;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class TypeCastException : ExpressionException
|
|
||||||
{
|
|
||||||
internal TypeCastException(Type fromType, Type toType)
|
|
||||||
: base(null, String.Empty)
|
|
||||||
{
|
|
||||||
FromType = fromType;
|
|
||||||
ToType = toType;
|
|
||||||
m_message = ExpressionResources.TypeCastErrorNoValue(fromType.Name, toType.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal TypeCastException(ISecretMasker secretMasker, Object value, ValueKind fromKind, ValueKind toKind)
|
|
||||||
: base(null, String.Empty)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
FromKind = fromKind;
|
|
||||||
ToKind = toKind;
|
|
||||||
m_message = ExpressionResources.TypeCastError(
|
|
||||||
fromKind, // from kind
|
|
||||||
toKind, // to kind
|
|
||||||
ExpressionUtil.FormatValue(secretMasker, value, fromKind)); // value
|
|
||||||
}
|
|
||||||
|
|
||||||
internal TypeCastException(ISecretMasker secretMasker, Object value, ValueKind fromKind, Type toType)
|
|
||||||
: base(null, String.Empty)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
FromKind = fromKind;
|
|
||||||
ToType = toType;
|
|
||||||
m_message = ExpressionResources.TypeCastError(
|
|
||||||
fromKind, // from kind
|
|
||||||
toType, // to type
|
|
||||||
ExpressionUtil.FormatValue(secretMasker, value, fromKind)); // value
|
|
||||||
}
|
|
||||||
|
|
||||||
internal TypeCastException(ISecretMasker secretMasker, Object value, ValueKind fromKind, Type toType, String error)
|
|
||||||
: base(null, String.Empty)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
FromKind = fromKind;
|
|
||||||
ToType = toType;
|
|
||||||
m_message = ExpressionResources.TypeCastErrorWithError(
|
|
||||||
fromKind, // from kind
|
|
||||||
toType, // to type
|
|
||||||
ExpressionUtil.FormatValue(secretMasker, value, fromKind), // value
|
|
||||||
secretMasker != null ? secretMasker.MaskSecrets(error) : error); // error
|
|
||||||
}
|
|
||||||
|
|
||||||
public override String Message => m_message;
|
|
||||||
|
|
||||||
internal Object Value { get; }
|
|
||||||
|
|
||||||
internal ValueKind? FromKind { get; }
|
|
||||||
|
|
||||||
internal Type FromType { get; }
|
|
||||||
|
|
||||||
internal ValueKind? ToKind { get; }
|
|
||||||
|
|
||||||
internal Type ToType { get; }
|
|
||||||
|
|
||||||
private readonly String m_message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class UnknownFunctionNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
// Should never reach here.
|
|
||||||
throw new NotSupportedException("Unknown function node is not supported during evaluation.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class UnknownNamedValueNode : NamedValueNode
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext)
|
|
||||||
{
|
|
||||||
// Should never reach here.
|
|
||||||
throw new NotSupportedException("Unknown function node is not supported during evaluation.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public enum ValueKind
|
|
||||||
{
|
|
||||||
Array,
|
|
||||||
Boolean,
|
|
||||||
DateTime,
|
|
||||||
Null,
|
|
||||||
Number,
|
|
||||||
Object,
|
|
||||||
String,
|
|
||||||
Version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions
|
|
||||||
{
|
|
||||||
internal sealed class XorNode : FunctionNode
|
|
||||||
{
|
|
||||||
protected sealed override Boolean TraceFullyRealized => false;
|
|
||||||
|
|
||||||
protected sealed override Object EvaluateCore(EvaluationContext context)
|
|
||||||
{
|
|
||||||
return Parameters[0].EvaluateBoolean(context) ^ Parameters[1].EvaluateBoolean(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -59,36 +59,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<TaskAttachment>> GetPlanAttachmentsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
string type,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("eb55e5d6-2f30-4295-b5ed-38da50b1fc52");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, type = type };
|
|
||||||
|
|
||||||
return SendAsync<List<TaskAttachment>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Preview API]
|
/// [Preview API]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -130,126 +100,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
content: content);
|
content: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="timelineId"></param>
|
|
||||||
/// <param name="recordId"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<TaskAttachment> GetAttachmentAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid timelineId,
|
|
||||||
Guid recordId,
|
|
||||||
string type,
|
|
||||||
string name,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("7898f959-9cdf-4096-b29e-7f293031629e");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, timelineId = timelineId, recordId = recordId, type = type, name = name };
|
|
||||||
|
|
||||||
return SendAsync<TaskAttachment>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="timelineId"></param>
|
|
||||||
/// <param name="recordId"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual async Task<Stream> GetAttachmentContentAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid timelineId,
|
|
||||||
Guid recordId,
|
|
||||||
string type,
|
|
||||||
string name,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("7898f959-9cdf-4096-b29e-7f293031629e");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, timelineId = timelineId, recordId = recordId, type = type, name = name };
|
|
||||||
HttpResponseMessage response;
|
|
||||||
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion("5.1-preview.1"),
|
|
||||||
mediaType: "application/octet-stream",
|
|
||||||
cancellationToken: cancellationToken).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
response = await SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, userState, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
if (response.Content.Headers.ContentEncoding.Contains("gzip", StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Stream responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
|
||||||
return new GZipStream(responseStream, CompressionMode.Decompress);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="timelineId"></param>
|
|
||||||
/// <param name="recordId"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<TaskAttachment>> GetAttachmentsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid timelineId,
|
|
||||||
Guid recordId,
|
|
||||||
string type,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("7898f959-9cdf-4096-b29e-7f293031629e");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, timelineId = timelineId, recordId = recordId, type = type };
|
|
||||||
|
|
||||||
return SendAsync<List<TaskAttachment>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Preview API]
|
/// [Preview API]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -290,35 +140,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="orchestrationId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public virtual Task<TaskAgentJob> GetJobInstanceAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
string orchestrationId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("0a1efd25-abda-43bd-9629-6c7bdd2e0d60");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, orchestrationId = orchestrationId };
|
|
||||||
|
|
||||||
return SendAsync<TaskAgentJob>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Preview API]
|
/// [Preview API]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -386,245 +207,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
content: content);
|
content: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="logId"></param>
|
|
||||||
/// <param name="startLine"></param>
|
|
||||||
/// <param name="endLine"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<string>> GetLogAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
int logId,
|
|
||||||
long? startLine = null,
|
|
||||||
long? endLine = null,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("46f5667d-263a-4684-91b1-dff7fdcf64e2");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, logId = logId };
|
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
|
||||||
if (startLine != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("startLine", startLine.Value.ToString(CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
if (endLine != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("endLine", endLine.Value.ToString(CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendAsync<List<string>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
queryParameters: queryParams,
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<TaskLog>> GetLogsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("46f5667d-263a-4684-91b1-dff7fdcf64e2");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
|
||||||
|
|
||||||
return SendAsync<List<TaskLog>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public virtual Task<List<TaskOrchestrationPlanGroupsQueueMetrics>> GetPlanGroupsQueueMetricsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("038fd4d5-cda7-44ca-92c0-935843fee1a7");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName };
|
|
||||||
|
|
||||||
return SendAsync<List<TaskOrchestrationPlanGroupsQueueMetrics>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="statusFilter"></param>
|
|
||||||
/// <param name="count"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public virtual Task<List<TaskOrchestrationQueuedPlanGroup>> GetQueuedPlanGroupsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
PlanGroupStatus? statusFilter = null,
|
|
||||||
int? count = null,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("0dd73091-3e36-4f43-b443-1b76dd426d84");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName };
|
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
|
||||||
if (statusFilter != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("statusFilter", statusFilter.Value.ToString());
|
|
||||||
}
|
|
||||||
if (count != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("count", count.Value.ToString(CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendAsync<List<TaskOrchestrationQueuedPlanGroup>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
queryParameters: queryParams,
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planGroup"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public virtual Task<TaskOrchestrationQueuedPlanGroup> GetQueuedPlanGroupAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
string planGroup,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("65fd0708-bc1e-447b-a731-0587c5464e5b");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planGroup = planGroup };
|
|
||||||
|
|
||||||
return SendAsync<TaskOrchestrationQueuedPlanGroup>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public virtual Task<TaskOrchestrationPlan> GetPlanAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("5cecd946-d704-471e-a45f-3b4064fcfaba");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
|
||||||
|
|
||||||
return SendAsync<TaskOrchestrationPlan>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="timelineId"></param>
|
|
||||||
/// <param name="changeId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<TimelineRecord>> GetRecordsAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid timelineId,
|
|
||||||
int? changeId = null,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("8893bc5b-35b2-4be7-83cb-99e683551db4");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, timelineId = timelineId };
|
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
|
||||||
if (changeId != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("changeId", changeId.Value.ToString(CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendAsync<List<TimelineRecord>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
queryParameters: queryParams,
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Preview API]
|
/// [Preview API]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -691,39 +273,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
content: content);
|
content: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="timelineId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual async Task DeleteTimelineAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid timelineId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("DELETE");
|
|
||||||
Guid locationId = new Guid("83597576-cc2c-453c-bea6-2882ae6a1653");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId, timelineId = timelineId };
|
|
||||||
|
|
||||||
using (HttpResponseMessage response = await SendAsync(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [Preview API]
|
/// [Preview API]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -768,33 +317,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<List<Timeline>> GetTimelinesAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("83597576-cc2c-453c-bea6-2882ae6a1653");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
|
||||||
|
|
||||||
return SendAsync<List<Timeline>>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
|
|
||||||
public static String ExpressionStringEscape(String value)
|
public static String ExpressionStringEscape(String value)
|
||||||
{
|
{
|
||||||
return Expressions.ExpressionUtil.StringEscape(value);
|
return Expressions2.Sdk.ExpressionUtility.StringEscape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String JsonStringEscape(String value)
|
public static String JsonStringEscape(String value)
|
||||||
|
|||||||
@@ -1,769 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public static class AgentJobRequestMessageUtil
|
|
||||||
{
|
|
||||||
// Legacy JobRequestMessage -> Pipeline JobRequestMessage
|
|
||||||
// Used by the agent when the latest version agent connect to old version TFS
|
|
||||||
// Used by the server when common method only take the new Message contact, like, telemetry logging
|
|
||||||
public static AgentJobRequestMessage Convert(WebApi.AgentJobRequestMessage message)
|
|
||||||
{
|
|
||||||
// construct steps
|
|
||||||
List<JobStep> jobSteps = new List<JobStep>();
|
|
||||||
foreach (var task in message.Tasks)
|
|
||||||
{
|
|
||||||
TaskStep taskStep = new TaskStep(task);
|
|
||||||
jobSteps.Add(taskStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<String, VariableValue> variables = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
HashSet<MaskHint> maskHints = new HashSet<MaskHint>();
|
|
||||||
JobResources jobResources = new JobResources();
|
|
||||||
WorkspaceOptions workspace = new WorkspaceOptions();
|
|
||||||
message.Environment.Extract(variables, maskHints, jobResources);
|
|
||||||
|
|
||||||
// convert repository endpoint into checkout task for Build
|
|
||||||
if (string.Equals(message.Plan.PlanType, "Build", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// repositoryId was added sometime after TFS2015, so we need to fall back to find endpoint using endpoint type.
|
|
||||||
var legacyRepoEndpoint = jobResources.Endpoints.FirstOrDefault(x => x.Data.ContainsKey("repositoryId"));
|
|
||||||
if (legacyRepoEndpoint == null)
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint = jobResources.Endpoints.FirstOrDefault(x => x.Type == LegacyRepositoryTypes.Bitbucket || x.Type == LegacyRepositoryTypes.Git || x.Type == LegacyRepositoryTypes.TfsGit || x.Type == LegacyRepositoryTypes.GitHub || x.Type == LegacyRepositoryTypes.GitHubEnterprise || x.Type == LegacyRepositoryTypes.TfsVersionControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build retention job will not have a repo endpoint.
|
|
||||||
if (legacyRepoEndpoint != null)
|
|
||||||
{
|
|
||||||
// construct checkout task
|
|
||||||
var checkoutStep = new TaskStep();
|
|
||||||
checkoutStep.Id = Guid.NewGuid();
|
|
||||||
checkoutStep.DisplayName = PipelineConstants.CheckoutTask.FriendlyName;
|
|
||||||
checkoutStep.Name = "__system_checkout";
|
|
||||||
checkoutStep.Reference = new TaskStepDefinitionReference()
|
|
||||||
{
|
|
||||||
Id = PipelineConstants.CheckoutTask.Id,
|
|
||||||
Name = PipelineConstants.CheckoutTask.Name,
|
|
||||||
Version = PipelineConstants.CheckoutTask.Version,
|
|
||||||
};
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Repository] = "__legacy_repo_endpoint";
|
|
||||||
|
|
||||||
// construct self repository resource
|
|
||||||
var defaultRepo = new RepositoryResource();
|
|
||||||
defaultRepo.Alias = "__legacy_repo_endpoint";
|
|
||||||
defaultRepo.Properties.Set<String>(RepositoryPropertyNames.Name, legacyRepoEndpoint.Name);
|
|
||||||
legacyRepoEndpoint.Data.TryGetValue("repositoryId", out string repositoryId);
|
|
||||||
if (!string.IsNullOrEmpty(repositoryId))
|
|
||||||
{
|
|
||||||
defaultRepo.Id = repositoryId;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
defaultRepo.Id = "__legacy_repo_endpoint";
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRepo.Endpoint = new ServiceEndpointReference()
|
|
||||||
{
|
|
||||||
Id = Guid.Empty,
|
|
||||||
Name = legacyRepoEndpoint.Name
|
|
||||||
};
|
|
||||||
defaultRepo.Type = ConvertLegacySourceType(legacyRepoEndpoint.Type);
|
|
||||||
defaultRepo.Url = legacyRepoEndpoint.Url;
|
|
||||||
if (variables.TryGetValue("build.sourceVersion", out VariableValue sourceVersion) && !string.IsNullOrEmpty(sourceVersion?.Value))
|
|
||||||
{
|
|
||||||
defaultRepo.Version = sourceVersion.Value;
|
|
||||||
}
|
|
||||||
if (variables.TryGetValue("build.sourceBranch", out VariableValue sourceBranch) && !string.IsNullOrEmpty(sourceBranch?.Value))
|
|
||||||
{
|
|
||||||
defaultRepo.Properties.Set<string>(RepositoryPropertyNames.Ref, sourceBranch.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
VersionInfo versionInfo = null;
|
|
||||||
if (variables.TryGetValue("build.sourceVersionAuthor", out VariableValue sourceAuthor) && !string.IsNullOrEmpty(sourceAuthor?.Value))
|
|
||||||
{
|
|
||||||
versionInfo = new VersionInfo();
|
|
||||||
versionInfo.Author = sourceAuthor.Value;
|
|
||||||
}
|
|
||||||
if (variables.TryGetValue("build.sourceVersionMessage", out VariableValue sourceMessage) && !string.IsNullOrEmpty(sourceMessage?.Value))
|
|
||||||
{
|
|
||||||
if (versionInfo == null)
|
|
||||||
{
|
|
||||||
versionInfo = new VersionInfo();
|
|
||||||
}
|
|
||||||
versionInfo.Message = sourceMessage.Value;
|
|
||||||
}
|
|
||||||
if (versionInfo != null)
|
|
||||||
{
|
|
||||||
defaultRepo.Properties.Set<VersionInfo>(RepositoryPropertyNames.VersionInfo, versionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultRepo.Type == RepositoryTypes.Tfvc)
|
|
||||||
{
|
|
||||||
if (variables.TryGetValue("build.sourceTfvcShelveset", out VariableValue shelveset) && !string.IsNullOrEmpty(shelveset?.Value))
|
|
||||||
{
|
|
||||||
defaultRepo.Properties.Set<string>(RepositoryPropertyNames.Shelveset, shelveset.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var legacyTfvcMappingJson = legacyRepoEndpoint.Data["tfvcWorkspaceMapping"];
|
|
||||||
var legacyTfvcMapping = JsonUtility.FromString<LegacyBuildWorkspace>(legacyTfvcMappingJson);
|
|
||||||
if (legacyTfvcMapping != null)
|
|
||||||
{
|
|
||||||
IList<WorkspaceMapping> tfvcMapping = new List<WorkspaceMapping>();
|
|
||||||
foreach (var mapping in legacyTfvcMapping.Mappings)
|
|
||||||
{
|
|
||||||
tfvcMapping.Add(new WorkspaceMapping() { ServerPath = mapping.ServerPath, LocalPath = mapping.LocalPath, Exclude = String.Equals(mapping.MappingType, "cloak", StringComparison.OrdinalIgnoreCase) });
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRepo.Properties.Set<IList<WorkspaceMapping>>(RepositoryPropertyNames.Mappings, tfvcMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (defaultRepo.Type == RepositoryTypes.Svn)
|
|
||||||
{
|
|
||||||
var legacySvnMappingJson = legacyRepoEndpoint.Data["svnWorkspaceMapping"];
|
|
||||||
var legacySvnMapping = JsonUtility.FromString<LegacySvnWorkspace>(legacySvnMappingJson);
|
|
||||||
if (legacySvnMapping != null)
|
|
||||||
{
|
|
||||||
IList<WorkspaceMapping> svnMapping = new List<WorkspaceMapping>();
|
|
||||||
foreach (var mapping in legacySvnMapping.Mappings)
|
|
||||||
{
|
|
||||||
svnMapping.Add(new WorkspaceMapping() { ServerPath = mapping.ServerPath, LocalPath = mapping.LocalPath, Depth = mapping.Depth, IgnoreExternals = mapping.IgnoreExternals, Revision = mapping.Revision });
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRepo.Properties.Set<IList<WorkspaceMapping>>(RepositoryPropertyNames.Mappings, svnMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
legacyRepoEndpoint.Data.TryGetValue("clean", out string cleanString);
|
|
||||||
if (!string.IsNullOrEmpty(cleanString))
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Clean] = cleanString;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Checkout task has clean set tp false as default.
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Clean] = Boolean.FalseString;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (legacyRepoEndpoint.Data.TryGetValue("checkoutSubmodules", out string checkoutSubmodulesString) &&
|
|
||||||
Boolean.TryParse(checkoutSubmodulesString, out Boolean checkoutSubmodules) &&
|
|
||||||
checkoutSubmodules)
|
|
||||||
{
|
|
||||||
if (legacyRepoEndpoint.Data.TryGetValue("checkoutNestedSubmodules", out string nestedSubmodulesString) &&
|
|
||||||
Boolean.TryParse(nestedSubmodulesString, out Boolean nestedSubmodules) &&
|
|
||||||
nestedSubmodules)
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Submodules] = PipelineConstants.CheckoutTaskInputs.SubmodulesOptions.Recursive;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Submodules] = PipelineConstants.CheckoutTaskInputs.SubmodulesOptions.True;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (legacyRepoEndpoint.Data.ContainsKey("fetchDepth"))
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.FetchDepth] = legacyRepoEndpoint.Data["fetchDepth"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (legacyRepoEndpoint.Data.ContainsKey("gitLfsSupport"))
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.Lfs] = legacyRepoEndpoint.Data["gitLfsSupport"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VariableUtility.GetEnableAccessTokenType(variables) == EnableAccessTokenType.Variable)
|
|
||||||
{
|
|
||||||
checkoutStep.Inputs[PipelineConstants.CheckoutTaskInputs.PersistCredentials] = Boolean.TrueString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct worksapce option
|
|
||||||
if (Boolean.TryParse(cleanString, out Boolean clean) && clean)
|
|
||||||
{
|
|
||||||
if (legacyRepoEndpoint.Data.TryGetValue("cleanOptions", out string cleanOptionsString) && !string.IsNullOrEmpty(cleanOptionsString))
|
|
||||||
{
|
|
||||||
if (string.Equals(cleanOptionsString, "1", StringComparison.OrdinalIgnoreCase)) //RepositoryCleanOptions.SourceAndOutputDir
|
|
||||||
{
|
|
||||||
workspace.Clean = PipelineConstants.WorkspaceCleanOptions.Outputs;
|
|
||||||
}
|
|
||||||
else if (string.Equals(cleanOptionsString, "2", StringComparison.OrdinalIgnoreCase)) //RepositoryCleanOptions.SourceDir
|
|
||||||
{
|
|
||||||
workspace.Clean = PipelineConstants.WorkspaceCleanOptions.Resources;
|
|
||||||
}
|
|
||||||
else if (string.Equals(cleanOptionsString, "3", StringComparison.OrdinalIgnoreCase)) //RepositoryCleanOptions.AllBuildDir
|
|
||||||
{
|
|
||||||
workspace.Clean = PipelineConstants.WorkspaceCleanOptions.All;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add checkout task when build.syncsources and skipSyncSource not set
|
|
||||||
variables.TryGetValue("build.syncSources", out VariableValue syncSourcesVariable);
|
|
||||||
legacyRepoEndpoint.Data.TryGetValue("skipSyncSource", out string skipSyncSource);
|
|
||||||
if (!string.IsNullOrEmpty(syncSourcesVariable?.Value) && Boolean.TryParse(syncSourcesVariable?.Value, out bool syncSource) && !syncSource)
|
|
||||||
{
|
|
||||||
checkoutStep.Condition = bool.FalseString;
|
|
||||||
}
|
|
||||||
else if (Boolean.TryParse(skipSyncSource, out bool skipSource) && skipSource)
|
|
||||||
{
|
|
||||||
checkoutStep.Condition = bool.FalseString;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobSteps.Insert(0, checkoutStep);
|
|
||||||
|
|
||||||
// always add self repository to job resource
|
|
||||||
jobResources.Repositories.Add(defaultRepo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AgentJobRequestMessage agentRequestMessage = new AgentJobRequestMessage(message.Plan, message.Timeline, message.JobId, message.JobName, message.JobRefName, null, null, null, variables, maskHints.ToList(), jobResources, null, workspace, jobSteps, null)
|
|
||||||
{
|
|
||||||
RequestId = message.RequestId
|
|
||||||
};
|
|
||||||
|
|
||||||
return agentRequestMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipeline JobRequestMessage -> Legacy JobRequestMessage
|
|
||||||
// Used by the server when the connected agent is old version and doesn't support new contract yet.
|
|
||||||
public static WebApi.AgentJobRequestMessage Convert(AgentJobRequestMessage message)
|
|
||||||
{
|
|
||||||
// Old agent can't handle container(s)
|
|
||||||
if (message.JobContainer != null)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Job containers are not supported");
|
|
||||||
}
|
|
||||||
if (message.JobServiceContainers != null)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Job service containers are not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old agent can't handle more than 1 repository
|
|
||||||
if (message.Resources.Repositories.Count > 1)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(string.Join(", ", message.Resources.Repositories.Select(x => x.Alias)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old agent can't handle more than 1 checkout task
|
|
||||||
if (message.Steps.Where(x => x.IsCheckoutTask()).Count() > 1)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(PipelineConstants.CheckoutTask.Id.ToString("D"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct tasks
|
|
||||||
List<TaskInstance> tasks = new List<TaskInstance>();
|
|
||||||
foreach (var step in message.Steps)
|
|
||||||
{
|
|
||||||
// Pipeline builder should add min agent demand when steps contains group
|
|
||||||
if (step.Type != StepType.Task)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(step.Type.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't add checkout task, we need to convert the checkout task into endpoint
|
|
||||||
if (!step.IsCheckoutTask())
|
|
||||||
{
|
|
||||||
TaskInstance task = (step as TaskStep).ToLegacyTaskInstance();
|
|
||||||
tasks.Add(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.Resources != null)
|
|
||||||
{
|
|
||||||
foreach (var endpoint in message.Resources.Endpoints)
|
|
||||||
{
|
|
||||||
// Legacy message require all endpoint's name equals to endpoint's id
|
|
||||||
// Guid.Empty is for repository endpoints
|
|
||||||
if (!String.Equals(endpoint.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
endpoint.Id != Guid.Empty)
|
|
||||||
{
|
|
||||||
endpoint.Name = endpoint.Id.ToString("D");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we propagate download ticket into the mask hints
|
|
||||||
foreach (var secureFile in message.Resources.SecureFiles)
|
|
||||||
{
|
|
||||||
if (!String.IsNullOrEmpty(secureFile.Ticket))
|
|
||||||
{
|
|
||||||
message.MaskHints.Add(new MaskHint() { Type = MaskType.Regex, Value = Regex.Escape(secureFile.Ticket) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String.Equals(message.Plan.PlanType, "Build", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// create repository endpoint base on checkout task + repository resource + repository endpoint
|
|
||||||
// repoResource might be null when environment verion is still on 1
|
|
||||||
var repoResource = message.Resources?.Repositories.SingleOrDefault();
|
|
||||||
if (repoResource != null)
|
|
||||||
{
|
|
||||||
var legacyRepoEndpoint = new ServiceEndpoint();
|
|
||||||
legacyRepoEndpoint.Name = repoResource.Properties.Get<string>(RepositoryPropertyNames.Name);
|
|
||||||
legacyRepoEndpoint.Type = ConvertToLegacySourceType(repoResource.Type);
|
|
||||||
legacyRepoEndpoint.Url = repoResource.Url;
|
|
||||||
if (repoResource.Endpoint != null)
|
|
||||||
{
|
|
||||||
var referencedEndpoint = message.Resources.Endpoints.First(x => (x.Id == repoResource.Endpoint.Id && x.Id != Guid.Empty) || (String.Equals(x.Name, repoResource.Endpoint.Name?.Literal, StringComparison.OrdinalIgnoreCase) && x.Id == Guid.Empty && repoResource.Endpoint.Id == Guid.Empty));
|
|
||||||
var endpointAuthCopy = referencedEndpoint.Authorization?.Clone();
|
|
||||||
if (endpointAuthCopy != null)
|
|
||||||
{
|
|
||||||
if (endpointAuthCopy.Scheme == EndpointAuthorizationSchemes.Token) //InstallationToken (Tabby) or ApiToken (GithubEnterprise)
|
|
||||||
{
|
|
||||||
if (referencedEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out string accessToken)) //Tabby
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Authorization = new EndpointAuthorization()
|
|
||||||
{
|
|
||||||
Scheme = EndpointAuthorizationSchemes.UsernamePassword,
|
|
||||||
Parameters =
|
|
||||||
{
|
|
||||||
{ EndpointAuthorizationParameters.Username, "x-access-token" },
|
|
||||||
{ EndpointAuthorizationParameters.Password, accessToken }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (referencedEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.ApiToken, out string apiToken)) //GithubEnterprise
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Authorization = new EndpointAuthorization()
|
|
||||||
{
|
|
||||||
Scheme = EndpointAuthorizationSchemes.UsernamePassword,
|
|
||||||
Parameters =
|
|
||||||
{
|
|
||||||
{ EndpointAuthorizationParameters.Username, apiToken },
|
|
||||||
{ EndpointAuthorizationParameters.Password, "x-oauth-basic" }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (endpointAuthCopy.Scheme == EndpointAuthorizationSchemes.PersonalAccessToken) // Github
|
|
||||||
{
|
|
||||||
if (referencedEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out string accessToken)) //Tabby
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Authorization = new EndpointAuthorization()
|
|
||||||
{
|
|
||||||
Scheme = EndpointAuthorizationSchemes.UsernamePassword,
|
|
||||||
Parameters =
|
|
||||||
{
|
|
||||||
{ EndpointAuthorizationParameters.Username, "pat" },
|
|
||||||
{ EndpointAuthorizationParameters.Password, accessToken }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Authorization = endpointAuthCopy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are 2 properties we put into the legacy repo endpoint directly from connect endpoint
|
|
||||||
if (referencedEndpoint.Data.TryGetValue("acceptUntrustedCerts", out String acceptUntrustedCerts))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["acceptUntrustedCerts"] = acceptUntrustedCerts;
|
|
||||||
}
|
|
||||||
if (referencedEndpoint.Data.TryGetValue("realmName", out String realmName))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["realmName"] = realmName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
legacyRepoEndpoint.Data["repositoryId"] = repoResource.Id;
|
|
||||||
|
|
||||||
// default values in the old message format
|
|
||||||
legacyRepoEndpoint.Data["clean"] = Boolean.FalseString;
|
|
||||||
legacyRepoEndpoint.Data["checkoutSubmodules"] = Boolean.FalseString;
|
|
||||||
legacyRepoEndpoint.Data["checkoutNestedSubmodules"] = Boolean.FalseString;
|
|
||||||
legacyRepoEndpoint.Data["fetchDepth"] = "0";
|
|
||||||
legacyRepoEndpoint.Data["gitLfsSupport"] = Boolean.FalseString;
|
|
||||||
legacyRepoEndpoint.Data["skipSyncSource"] = Boolean.FalseString;
|
|
||||||
legacyRepoEndpoint.Data["cleanOptions"] = "0";
|
|
||||||
legacyRepoEndpoint.Data["rootFolder"] = null; // old tfvc repo endpoint has this set to $/foo, but it doesn't seems to be used at all.
|
|
||||||
|
|
||||||
if (repoResource.Type == RepositoryTypes.Tfvc)
|
|
||||||
{
|
|
||||||
var tfvcMapping = repoResource.Properties.Get<IList<WorkspaceMapping>>(RepositoryPropertyNames.Mappings);
|
|
||||||
if (tfvcMapping != null)
|
|
||||||
{
|
|
||||||
LegacyBuildWorkspace legacyMapping = new LegacyBuildWorkspace();
|
|
||||||
foreach (var mapping in tfvcMapping)
|
|
||||||
{
|
|
||||||
legacyMapping.Mappings.Add(new LegacyMappingDetails() { ServerPath = mapping.ServerPath, LocalPath = mapping.LocalPath, MappingType = mapping.Exclude ? "cloak" : "map" });
|
|
||||||
}
|
|
||||||
|
|
||||||
legacyRepoEndpoint.Data["tfvcWorkspaceMapping"] = JsonUtility.ToString(legacyMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (repoResource.Type == RepositoryTypes.Svn)
|
|
||||||
{
|
|
||||||
var svnMapping = repoResource.Properties.Get<IList<WorkspaceMapping>>(RepositoryPropertyNames.Mappings);
|
|
||||||
if (svnMapping != null)
|
|
||||||
{
|
|
||||||
LegacySvnWorkspace legacyMapping = new LegacySvnWorkspace();
|
|
||||||
foreach (var mapping in svnMapping)
|
|
||||||
{
|
|
||||||
legacyMapping.Mappings.Add(new LegacySvnMappingDetails() { ServerPath = mapping.ServerPath, LocalPath = mapping.LocalPath, Depth = mapping.Depth, IgnoreExternals = mapping.IgnoreExternals, Revision = mapping.Revision });
|
|
||||||
}
|
|
||||||
|
|
||||||
legacyRepoEndpoint.Data["svnWorkspaceMapping"] = JsonUtility.ToString(legacyMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (repoResource.Type == RepositoryTypes.Git)
|
|
||||||
{
|
|
||||||
if (message.Variables.TryGetValue(WellKnownDistributedTaskVariables.ServerType, out VariableValue serverType) && String.Equals(serverType?.Value, "Hosted", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["onpremtfsgit"] = Boolean.FalseString;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["onpremtfsgit"] = Boolean.TrueString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Variables.ContainsKey("build.repository.id") || String.IsNullOrEmpty(message.Variables["build.repository.id"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.repository.id"] = repoResource.Id;
|
|
||||||
}
|
|
||||||
if (!message.Variables.ContainsKey("build.repository.name") || String.IsNullOrEmpty(message.Variables["build.repository.name"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.repository.name"] = repoResource.Properties.Get<String>(RepositoryPropertyNames.Name);
|
|
||||||
}
|
|
||||||
if (!message.Variables.ContainsKey("build.repository.uri") || String.IsNullOrEmpty(message.Variables["build.repository.uri"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.repository.uri"] = repoResource.Url.AbsoluteUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionInfo = repoResource.Properties.Get<VersionInfo>(RepositoryPropertyNames.VersionInfo);
|
|
||||||
if (!message.Variables.ContainsKey("build.sourceVersionAuthor") || String.IsNullOrEmpty(message.Variables["build.sourceVersionAuthor"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.sourceVersionAuthor"] = versionInfo?.Author;
|
|
||||||
}
|
|
||||||
if (!message.Variables.ContainsKey("build.sourceVersionMessage") || String.IsNullOrEmpty(message.Variables["build.sourceVersionMessage"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.sourceVersionMessage"] = versionInfo?.Message;
|
|
||||||
}
|
|
||||||
if (!message.Variables.ContainsKey("build.sourceVersion") || String.IsNullOrEmpty(message.Variables["build.sourceVersion"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.sourceVersion"] = repoResource.Version;
|
|
||||||
}
|
|
||||||
if (!message.Variables.ContainsKey("build.sourceBranch") || String.IsNullOrEmpty(message.Variables["build.sourceBranch"]?.Value))
|
|
||||||
{
|
|
||||||
message.Variables["build.sourceBranch"] = repoResource.Properties.Get<String>(RepositoryPropertyNames.Ref);
|
|
||||||
}
|
|
||||||
if (repoResource.Type == RepositoryTypes.Tfvc)
|
|
||||||
{
|
|
||||||
var shelveset = repoResource.Properties.Get<String>(RepositoryPropertyNames.Shelveset);
|
|
||||||
if (!String.IsNullOrEmpty(shelveset) && (!message.Variables.ContainsKey("build.sourceTfvcShelveset") || String.IsNullOrEmpty(message.Variables["build.sourceTfvcShelveset"]?.Value)))
|
|
||||||
{
|
|
||||||
message.Variables["build.sourceTfvcShelveset"] = shelveset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskStep checkoutTask = message.Steps.FirstOrDefault(x => x.IsCheckoutTask()) as TaskStep;
|
|
||||||
if (checkoutTask != null)
|
|
||||||
{
|
|
||||||
if (checkoutTask.Inputs.TryGetValue(PipelineConstants.CheckoutTaskInputs.Clean, out string taskInputClean) && !string.IsNullOrEmpty(taskInputClean))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["clean"] = taskInputClean;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["clean"] = Boolean.FalseString;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkoutTask.Inputs.TryGetValue(PipelineConstants.CheckoutTaskInputs.Submodules, out string taskInputSubmodules) && !string.IsNullOrEmpty(taskInputSubmodules))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["checkoutSubmodules"] = Boolean.TrueString;
|
|
||||||
if (String.Equals(taskInputSubmodules, PipelineConstants.CheckoutTaskInputs.SubmodulesOptions.Recursive, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["checkoutNestedSubmodules"] = Boolean.TrueString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkoutTask.Inputs.TryGetValue(PipelineConstants.CheckoutTaskInputs.FetchDepth, out string taskInputFetchDepth) && !string.IsNullOrEmpty(taskInputFetchDepth))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["fetchDepth"] = taskInputFetchDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkoutTask.Inputs.TryGetValue(PipelineConstants.CheckoutTaskInputs.Lfs, out string taskInputfs) && !string.IsNullOrEmpty(taskInputfs))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["gitLfsSupport"] = taskInputfs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip sync sources
|
|
||||||
if (String.Equals(checkoutTask.Inputs[PipelineConstants.CheckoutTaskInputs.Repository], PipelineConstants.NoneAlias, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["skipSyncSource"] = Boolean.TrueString;
|
|
||||||
}
|
|
||||||
else if (String.Equals(checkoutTask.Inputs[PipelineConstants.CheckoutTaskInputs.Repository], PipelineConstants.DesignerRepo, StringComparison.OrdinalIgnoreCase) && checkoutTask.Condition == Boolean.FalseString)
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["skipSyncSource"] = Boolean.TrueString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// workspace clean options
|
|
||||||
legacyRepoEndpoint.Data["cleanOptions"] = "0"; // RepositoryCleanOptions.Source;
|
|
||||||
if (message.Workspace != null)
|
|
||||||
{
|
|
||||||
if (String.Equals(message.Workspace.Clean, PipelineConstants.WorkspaceCleanOptions.Outputs, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["cleanOptions"] = "1"; // RepositoryCleanOptions.SourceAndOutputDir;
|
|
||||||
}
|
|
||||||
else if (String.Equals(message.Workspace.Clean, PipelineConstants.WorkspaceCleanOptions.Resources, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["cleanOptions"] = "2"; //RepositoryCleanOptions.SourceDir;
|
|
||||||
}
|
|
||||||
else if (String.Equals(message.Workspace.Clean, PipelineConstants.WorkspaceCleanOptions.All, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
legacyRepoEndpoint.Data["cleanOptions"] = "3"; // RepositoryCleanOptions.AllBuildDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add reposiotry endpoint to environment
|
|
||||||
message.Resources.Endpoints.Add(legacyRepoEndpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JobEnvironment environment = new JobEnvironment(message.Variables, message.MaskHints, message.Resources);
|
|
||||||
|
|
||||||
WebApi.AgentJobRequestMessage legacyAgentRequestMessage = new WebApi.AgentJobRequestMessage(message.Plan, message.Timeline, message.JobId, message.JobDisplayName, message.JobName, environment, tasks)
|
|
||||||
{
|
|
||||||
RequestId = message.RequestId
|
|
||||||
};
|
|
||||||
|
|
||||||
return legacyAgentRequestMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertLegacySourceType(string legacySourceType)
|
|
||||||
{
|
|
||||||
if (String.Equals(legacySourceType, LegacyRepositoryTypes.Bitbucket, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.Bitbucket;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.Git, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.ExternalGit;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.TfsGit, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.Git;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.GitHub;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.GitHubEnterprise, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.GitHubEnterprise;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.Svn, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.Svn;
|
|
||||||
}
|
|
||||||
else if (String.Equals(legacySourceType, LegacyRepositoryTypes.TfsVersionControl, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return RepositoryTypes.Tfvc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(legacySourceType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertToLegacySourceType(string pipelineSourceType)
|
|
||||||
{
|
|
||||||
if (String.Equals(pipelineSourceType, RepositoryTypes.Bitbucket, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.Bitbucket;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.ExternalGit, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.Git;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.Git, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.TfsGit;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.GitHub;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.GitHubEnterprise, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.GitHubEnterprise;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.Svn, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.Svn;
|
|
||||||
}
|
|
||||||
else if (String.Equals(pipelineSourceType, RepositoryTypes.Tfvc, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return LegacyRepositoryTypes.TfsVersionControl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(pipelineSourceType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LegacyRepositoryTypes // Copy from Build.Webapi
|
|
||||||
{
|
|
||||||
public const String TfsVersionControl = "TfsVersionControl";
|
|
||||||
public const String TfsGit = "TfsGit";
|
|
||||||
public const String Git = "Git";
|
|
||||||
public const String GitHub = "GitHub";
|
|
||||||
public const String GitHubEnterprise = "GitHubEnterprise";
|
|
||||||
public const String Bitbucket = "Bitbucket";
|
|
||||||
public const String Svn = "Svn";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an entry in a workspace mapping.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
private class LegacyMappingDetails
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The server path.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "serverPath")]
|
|
||||||
public String ServerPath
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The mapping type.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "mappingType")]
|
|
||||||
public String MappingType
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The local path.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "localPath")]
|
|
||||||
public String LocalPath
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a workspace mapping.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
private class LegacyBuildWorkspace
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The list of workspace mapping entries.
|
|
||||||
/// </summary>
|
|
||||||
public List<LegacyMappingDetails> Mappings
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_mappings == null)
|
|
||||||
{
|
|
||||||
m_mappings = new List<LegacyMappingDetails>();
|
|
||||||
}
|
|
||||||
return m_mappings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "mappings")]
|
|
||||||
private List<LegacyMappingDetails> m_mappings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a Subversion mapping entry.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
private class LegacySvnMappingDetails
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The server path.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "serverPath")]
|
|
||||||
public String ServerPath
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The local path.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "localPath")]
|
|
||||||
public String LocalPath
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The revision.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "revision")]
|
|
||||||
public String Revision
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The depth.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "depth")]
|
|
||||||
public Int32 Depth
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether to ignore externals.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(Name = "ignoreExternals")]
|
|
||||||
public bool IgnoreExternals
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a subversion workspace.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
private class LegacySvnWorkspace
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The list of mappings.
|
|
||||||
/// </summary>
|
|
||||||
public List<LegacySvnMappingDetails> Mappings
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_Mappings == null)
|
|
||||||
{
|
|
||||||
m_Mappings = new List<LegacySvnMappingDetails>();
|
|
||||||
}
|
|
||||||
return m_Mappings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "mappings")]
|
|
||||||
private List<LegacySvnMappingDetails> m_Mappings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class AgentPoolReference : ResourceReference
|
|
||||||
{
|
|
||||||
public AgentPoolReference()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentPoolReference(AgentPoolReference referenceToCopy)
|
|
||||||
: base(referenceToCopy)
|
|
||||||
{
|
|
||||||
this.Id = referenceToCopy.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Int32 Id
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentPoolReference Clone()
|
|
||||||
{
|
|
||||||
return new AgentPoolReference(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override String ToString()
|
|
||||||
{
|
|
||||||
return base.ToString() ?? this.Id.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class AgentPoolStore : IAgentPoolStore
|
|
||||||
{
|
|
||||||
public AgentPoolStore(
|
|
||||||
IList<TaskAgentPool> pools,
|
|
||||||
IAgentPoolResolver resolver = null)
|
|
||||||
{
|
|
||||||
this.Resolver = resolver;
|
|
||||||
Add(pools?.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the queue resolver configured for this store.
|
|
||||||
/// </summary>
|
|
||||||
public IAgentPoolResolver Resolver
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Authorize(IList<AgentPoolReference> pools)
|
|
||||||
{
|
|
||||||
if (pools?.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var pool in pools)
|
|
||||||
{
|
|
||||||
var authorizedResource = this.Resolver?.Resolve(pool);
|
|
||||||
if (authorizedResource != null)
|
|
||||||
{
|
|
||||||
Add(authorizedResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<AgentPoolReference> GetAuthorizedReferences()
|
|
||||||
{
|
|
||||||
return m_resourcesById.Values.Select(x => new AgentPoolReference { Id = x.Id }).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskAgentPool Get(AgentPoolReference reference)
|
|
||||||
{
|
|
||||||
if (reference == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var referenceId = reference.Id;
|
|
||||||
var referenceName = reference.Name?.Literal;
|
|
||||||
if (reference.Id == 0 && String.IsNullOrEmpty(referenceName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskAgentPool authorizedResource = null;
|
|
||||||
if (referenceId != 0)
|
|
||||||
{
|
|
||||||
if (m_resourcesById.TryGetValue(referenceId, out authorizedResource))
|
|
||||||
{
|
|
||||||
return authorizedResource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!String.IsNullOrEmpty(referenceName))
|
|
||||||
{
|
|
||||||
if (m_resourcesByName.TryGetValue(referenceName, out authorizedResource))
|
|
||||||
{
|
|
||||||
return authorizedResource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an authorizer then attempt to authorize the reference for use
|
|
||||||
authorizedResource = this.Resolver?.Resolve(reference);
|
|
||||||
if (authorizedResource != null)
|
|
||||||
{
|
|
||||||
Add(authorizedResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorizedResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Add(params TaskAgentPool[] resources)
|
|
||||||
{
|
|
||||||
if (resources?.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var resource in resources)
|
|
||||||
{
|
|
||||||
// Track by ID
|
|
||||||
if (m_resourcesById.TryGetValue(resource.Id, out _))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_resourcesById.Add(resource.Id, resource);
|
|
||||||
|
|
||||||
// Track by name
|
|
||||||
if (m_resourcesByName.TryGetValue(resource.Name, out _))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_resourcesByName.Add(resource.Name, resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<Int32, TaskAgentPool> m_resourcesById = new Dictionary<Int32, TaskAgentPool>();
|
|
||||||
private readonly Dictionary<String, TaskAgentPool> m_resourcesByName = new Dictionary<String, TaskAgentPool>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Runtime;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Validation;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class AgentPoolTarget : PhaseTarget
|
|
||||||
{
|
|
||||||
public AgentPoolTarget()
|
|
||||||
: base(PhaseTargetType.Pool)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentPoolTarget(AgentPoolTarget targetToClone)
|
|
||||||
: base(targetToClone)
|
|
||||||
{
|
|
||||||
this.Pool = targetToClone.Pool?.Clone();
|
|
||||||
|
|
||||||
|
|
||||||
if (targetToClone.AgentSpecification != null)
|
|
||||||
{
|
|
||||||
this.AgentSpecification = new JObject(targetToClone.AgentSpecification);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetToClone.m_agentIds?.Count > 0)
|
|
||||||
{
|
|
||||||
this.m_agentIds = targetToClone.m_agentIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target pool from which agents will be selected.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public AgentPoolReference Pool
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public JObject AgentSpecification
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets agent Ids filter on which deployment should be done.
|
|
||||||
/// </summary>
|
|
||||||
public List<Int32> AgentIds
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_agentIds == null)
|
|
||||||
{
|
|
||||||
m_agentIds = new List<Int32>();
|
|
||||||
}
|
|
||||||
return m_agentIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override PhaseTarget Clone()
|
|
||||||
{
|
|
||||||
return new AgentPoolTarget(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean IsValid(TaskDefinition task)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(task, nameof(task));
|
|
||||||
return task.RunsOn.Contains(TaskRunsOnConstants.RunsOnAgent, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Validate(
|
|
||||||
IPipelineContext context,
|
|
||||||
BuildOptions buildOptions,
|
|
||||||
ValidationResult result,
|
|
||||||
IList<Step> steps,
|
|
||||||
ISet<Demand> taskDemands)
|
|
||||||
{
|
|
||||||
// validate pool
|
|
||||||
Int32 poolId = 0;
|
|
||||||
String poolName = null;
|
|
||||||
var pool = this.Pool;
|
|
||||||
if (pool != null)
|
|
||||||
{
|
|
||||||
poolId = pool.Id;
|
|
||||||
poolName = pool.Name?.GetValue(context)?.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poolId == 0 && String.IsNullOrEmpty(poolName) && buildOptions.ValidateResources)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotDefined()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// we have a valid queue. record the reference
|
|
||||||
result.AddPoolReference(poolId, poolName);
|
|
||||||
|
|
||||||
// Attempt to resolve the queue using any identifier specified. We will look up by either ID
|
|
||||||
// or name and the ID is preferred since it is immutable and more specific.
|
|
||||||
if (buildOptions.ValidateResources)
|
|
||||||
{
|
|
||||||
TaskAgentPool taskAgentPool = null;
|
|
||||||
var resourceStore = context.ResourceStore;
|
|
||||||
if (resourceStore != null)
|
|
||||||
{
|
|
||||||
if (poolId != 0)
|
|
||||||
{
|
|
||||||
taskAgentPool = resourceStore.GetPool(poolId);
|
|
||||||
if (taskAgentPool == null)
|
|
||||||
{
|
|
||||||
result.UnauthorizedResources.Pools.Add(new AgentPoolReference { Id = poolId });
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFound(poolId)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!String.IsNullOrEmpty(poolName))
|
|
||||||
{
|
|
||||||
taskAgentPool = resourceStore.GetPool(poolName);
|
|
||||||
if (taskAgentPool == null)
|
|
||||||
{
|
|
||||||
result.UnauthorizedResources.Pools.Add(new AgentPoolReference { Name = poolName });
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFound(poolName)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the resolved values inline to the resolved resource for this validation run
|
|
||||||
if (taskAgentPool != null)
|
|
||||||
{
|
|
||||||
this.Pool.Id = taskAgentPool.Id;
|
|
||||||
this.Pool.Name = taskAgentPool.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override JobExecutionContext CreateJobContext(PhaseExecutionContext context, string jobName, int attempt, bool continueOnError, int timeoutInMinutes, int cancelTimeoutInMinutes, IJobFactory jobFactory)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(nameof(AgentPoolTarget));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override ExpandPhaseResult Expand(PhaseExecutionContext context, bool continueOnError, int timeoutInMinutes, int cancelTimeoutInMinutes, IJobFactory jobFactory, JobExpansionOptions options)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(nameof(AgentPoolTarget));
|
|
||||||
}
|
|
||||||
|
|
||||||
[OnSerializing]
|
|
||||||
private void OnSerializing(StreamingContext context)
|
|
||||||
{
|
|
||||||
if (m_agentIds?.Count == 0)
|
|
||||||
{
|
|
||||||
m_agentIds = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "AgentIds", EmitDefaultValue = false)]
|
|
||||||
private List<Int32> m_agentIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class AgentQueueReference : ResourceReference
|
|
||||||
{
|
|
||||||
public AgentQueueReference()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentQueueReference(AgentQueueReference referenceToCopy)
|
|
||||||
: base(referenceToCopy)
|
|
||||||
{
|
|
||||||
this.Id = referenceToCopy.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Int32 Id
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentQueueReference Clone()
|
|
||||||
{
|
|
||||||
return new AgentQueueReference(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override String ToString()
|
|
||||||
{
|
|
||||||
return base.ToString() ?? this.Id.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class AgentQueueStore : IAgentQueueStore
|
|
||||||
{
|
|
||||||
public AgentQueueStore(
|
|
||||||
IList<TaskAgentQueue> queues,
|
|
||||||
IAgentQueueResolver resolver = null)
|
|
||||||
{
|
|
||||||
this.Resolver = resolver;
|
|
||||||
Add(queues?.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the queue resolver configured for this store.
|
|
||||||
/// </summary>
|
|
||||||
public IAgentQueueResolver Resolver
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Authorize(IList<TaskAgentQueue> queues)
|
|
||||||
{
|
|
||||||
if (queues?.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var queue in queues)
|
|
||||||
{
|
|
||||||
Add(queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IList<AgentQueueReference> GetAuthorizedReferences()
|
|
||||||
{
|
|
||||||
return m_resourcesById.Values.Select(x => new AgentQueueReference { Id = x.Id }).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskAgentQueue Get(AgentQueueReference reference)
|
|
||||||
{
|
|
||||||
if (reference == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var referenceId = reference.Id;
|
|
||||||
var referenceName = reference.Name?.Literal;
|
|
||||||
if (reference.Id == 0 && String.IsNullOrEmpty(referenceName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskAgentQueue authorizedResource = null;
|
|
||||||
if (referenceId != 0)
|
|
||||||
{
|
|
||||||
if (m_resourcesById.TryGetValue(referenceId, out authorizedResource))
|
|
||||||
{
|
|
||||||
return authorizedResource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!String.IsNullOrEmpty(referenceName))
|
|
||||||
{
|
|
||||||
if (m_resourcesByName.TryGetValue(referenceName, out List<TaskAgentQueue> matchingResources))
|
|
||||||
{
|
|
||||||
if (matchingResources.Count > 1)
|
|
||||||
{
|
|
||||||
throw new AmbiguousResourceSpecificationException(PipelineStrings.AmbiguousServiceEndpointSpecification(referenceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchingResources[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an authorizer then attempt to authorize the reference for use
|
|
||||||
authorizedResource = this.Resolver?.Resolve(reference);
|
|
||||||
if (authorizedResource != null)
|
|
||||||
{
|
|
||||||
Add(authorizedResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorizedResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Add(params TaskAgentQueue[] resources)
|
|
||||||
{
|
|
||||||
if (resources?.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var resource in resources)
|
|
||||||
{
|
|
||||||
// Track by ID
|
|
||||||
if (m_resourcesById.TryGetValue(resource.Id, out _))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_resourcesById.Add(resource.Id, resource);
|
|
||||||
|
|
||||||
// not all references have names
|
|
||||||
var name = resource.Name;
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track by name
|
|
||||||
if (!m_resourcesByName.TryGetValue(name, out var list))
|
|
||||||
{
|
|
||||||
list = new List<TaskAgentQueue>();
|
|
||||||
m_resourcesByName.Add(name, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clobber previously added alternate name, with the real hosted queue.
|
|
||||||
// For example, during the "Hosted macOS High Sierra" transition, until the real queue
|
|
||||||
// existed, it was treated as an alternate name for the "Hosted macOS" queue. After the
|
|
||||||
// real "Hosted macOS High Sierra" queue was created, it took priority.
|
|
||||||
if (list.Count > 0 && list[0].Pool?.IsHosted == true && resource.Pool?.IsHosted == true)
|
|
||||||
{
|
|
||||||
list[0] = resource;
|
|
||||||
}
|
|
||||||
// Otherwise add the queue
|
|
||||||
else
|
|
||||||
{
|
|
||||||
list.Add(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track by alternate name for specific hosted pools.
|
|
||||||
// For example, "Hosted macOS Preview" and "Hosted macOS" are equivalent.
|
|
||||||
if (resource.Pool?.IsHosted == true && s_alternateNames.TryGetValue(name, out var alternateNames))
|
|
||||||
{
|
|
||||||
foreach (var alternateName in alternateNames)
|
|
||||||
{
|
|
||||||
if (!m_resourcesByName.TryGetValue(alternateName, out list))
|
|
||||||
{
|
|
||||||
list = new List<TaskAgentQueue>();
|
|
||||||
m_resourcesByName.Add(alternateName, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list.Count == 0 || list[0].Pool?.IsHosted != true)
|
|
||||||
{
|
|
||||||
list.Add(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Dictionary<String, String[]> s_alternateNames = new Dictionary<String, String[]>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
{ "Hosted macOS", new[] { "Hosted macOS Preview" } },
|
|
||||||
{ "Hosted macOS Preview", new[] { "Hosted macOS" } },
|
|
||||||
};
|
|
||||||
private readonly Dictionary<Int32, TaskAgentQueue> m_resourcesById = new Dictionary<Int32, TaskAgentQueue>();
|
|
||||||
private readonly Dictionary<String, List<TaskAgentQueue>> m_resourcesByName = new Dictionary<String, List<TaskAgentQueue>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,647 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Runtime;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Validation;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides options for phase execution on an agent within a queue.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class AgentQueueTarget : PhaseTarget
|
|
||||||
{
|
|
||||||
public AgentQueueTarget()
|
|
||||||
: base(PhaseTargetType.Queue)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentQueueTarget(AgentQueueTarget targetToClone)
|
|
||||||
: base(targetToClone)
|
|
||||||
{
|
|
||||||
this.Queue = targetToClone.Queue?.Clone();
|
|
||||||
this.Execution = targetToClone.Execution?.Clone();
|
|
||||||
|
|
||||||
if (targetToClone.AgentSpecification != null)
|
|
||||||
{
|
|
||||||
this.AgentSpecification = new JObject(targetToClone.AgentSpecification);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetToClone.SidecarContainers?.Count > 0)
|
|
||||||
{
|
|
||||||
m_sidecarContainers = new Dictionary<String, ExpressionValue<String>>(targetToClone.SidecarContainers, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target queue from which agents will be selected.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
[JsonConverter(typeof(QueueJsonConverter))]
|
|
||||||
public AgentQueueReference Queue
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public JObject AgentSpecification
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets parallel execution options which control expansion and execution of the phase.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public ParallelExecutionOptions Execution
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets workspace options which control how agent manage the workspace of the phase.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public WorkspaceOptions Workspace
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the container the phase will be run in.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
[JsonConverter(typeof(ExpressionValueJsonConverter<String>))]
|
|
||||||
public ExpressionValue<String> Container
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the sidecar containers that will run alongside the phase.
|
|
||||||
/// </summary>
|
|
||||||
public IDictionary<String, ExpressionValue<String>> SidecarContainers
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_sidecarContainers == null)
|
|
||||||
{
|
|
||||||
m_sidecarContainers = new Dictionary<String, ExpressionValue<String>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
return m_sidecarContainers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override PhaseTarget Clone()
|
|
||||||
{
|
|
||||||
return new AgentQueueTarget(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean IsValid(TaskDefinition task)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(task, nameof(task));
|
|
||||||
return task.RunsOn.Contains(TaskRunsOnConstants.RunsOnAgent, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a clone of this and attempts to resolve all expressions and macros.
|
|
||||||
/// </summary>
|
|
||||||
internal AgentQueueTarget Evaluate(
|
|
||||||
IPipelineContext context,
|
|
||||||
ValidationResult result)
|
|
||||||
{
|
|
||||||
var qname = String.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
qname = context.ExpandVariables(this.Queue?.Name?.GetValue(context).Value);
|
|
||||||
}
|
|
||||||
catch (DistributedTask.Expressions.ExpressionException ee)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(ee.Message));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var literalTarget = this.Clone() as AgentQueueTarget;
|
|
||||||
|
|
||||||
var spec = this.AgentSpecification;
|
|
||||||
if (spec != null)
|
|
||||||
{
|
|
||||||
spec = context.Evaluate(this.AgentSpecification).Value;
|
|
||||||
literalTarget.AgentSpecification = spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note! The "vmImage" token of the agent spec is currently treated specially.
|
|
||||||
// This is a temporary relationship that allows vmImage agent specs to specify
|
|
||||||
// the hosted pool to use.
|
|
||||||
// It would be better to factor out this work into a separate, plug-in validator.
|
|
||||||
if (String.IsNullOrEmpty(qname) && spec != null)
|
|
||||||
{
|
|
||||||
const string VMImage = "vmImage"; // should be: YamlConstants.VMImage, which is inaccessible :(
|
|
||||||
spec.TryGetValue(VMImage, out var token);
|
|
||||||
if (token != null && token.Type == JTokenType.String)
|
|
||||||
{
|
|
||||||
var rawTokenValue = token.Value<String>();
|
|
||||||
var resolvedPoolName = PoolNameForVMImage(rawTokenValue);
|
|
||||||
if (resolvedPoolName == null)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError($"Unexpected vmImage '{rawTokenValue}'"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
spec.Remove(VMImage);
|
|
||||||
literalTarget.Queue = new AgentQueueReference
|
|
||||||
{
|
|
||||||
Name = resolvedPoolName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
literalTarget.Queue.Name = qname;
|
|
||||||
}
|
|
||||||
|
|
||||||
return literalTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// returns true for strings structured like expressions or macros.
|
|
||||||
/// they could techincally be literals though.
|
|
||||||
/// </summary>
|
|
||||||
internal static Boolean IsProbablyExpressionOrMacro(String s)
|
|
||||||
{
|
|
||||||
return ExpressionValue.IsExpression(s) || VariableUtility.IsVariable(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// returns true if this model is composed only of literal values (no expressions)
|
|
||||||
/// </summary>
|
|
||||||
internal Boolean IsLiteral()
|
|
||||||
{
|
|
||||||
var queue = this.Queue;
|
|
||||||
if (queue != null)
|
|
||||||
{
|
|
||||||
var queueName = queue.Name;
|
|
||||||
if (queueName != null)
|
|
||||||
{
|
|
||||||
if (!queueName.IsLiteral || VariableUtility.IsVariable(queueName.Literal))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var spec = this.AgentSpecification;
|
|
||||||
if (spec != null)
|
|
||||||
{
|
|
||||||
bool IsLiteral(JObject o)
|
|
||||||
{
|
|
||||||
foreach (var pair in o)
|
|
||||||
{
|
|
||||||
switch (pair.Value.Type)
|
|
||||||
{
|
|
||||||
case JTokenType.String:
|
|
||||||
if (IsProbablyExpressionOrMacro(pair.Value.Value<String>()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case JTokenType.Object:
|
|
||||||
if (!IsLiteral(pair.Value.Value<JObject>()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsLiteral(spec))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Temporary code to translate vmImage. Pool providers work will move this to a different layer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vmImageValue"></param>
|
|
||||||
/// <returns>Hosted pool name</returns>
|
|
||||||
internal static String PoolNameForVMImage(String vmImageValue)
|
|
||||||
{
|
|
||||||
switch ((vmImageValue ?? String.Empty).ToUpperInvariant())
|
|
||||||
{
|
|
||||||
case "UBUNTU 16.04":
|
|
||||||
case "UBUNTU-16.04":
|
|
||||||
case "UBUNTU LATEST":
|
|
||||||
case "UBUNTU-LATEST":
|
|
||||||
return "Hosted Ubuntu 1604";
|
|
||||||
case "UBUNTU 18.04":
|
|
||||||
case "UBUNTU-18.04":
|
|
||||||
return "Hosted Ubuntu 1804";
|
|
||||||
case "VISUAL STUDIO 2015 ON WINDOWS SERVER 2012R2":
|
|
||||||
case "VS2015-WIN2012R2":
|
|
||||||
return "Hosted";
|
|
||||||
case "VISUAL STUDIO 2017 ON WINDOWS SERVER 2016":
|
|
||||||
case "VS2017-WIN2016":
|
|
||||||
return "Hosted VS2017";
|
|
||||||
case "WINDOWS-2019-VS2019":
|
|
||||||
case "WINDOWS-2019":
|
|
||||||
case "WINDOWS LATEST":
|
|
||||||
case "WINDOWS-LATEST":
|
|
||||||
return "Hosted Windows 2019 with VS2019";
|
|
||||||
case "WINDOWS SERVER 1803":
|
|
||||||
case "WIN1803":
|
|
||||||
return "Hosted Windows Container";
|
|
||||||
case "MACOS 10.13":
|
|
||||||
case "MACOS-10.13":
|
|
||||||
case "XCODE 9 ON MACOS 10.13":
|
|
||||||
case "XCODE9-MACOS10.13":
|
|
||||||
case "XCODE 10 ON MACOS 10.13":
|
|
||||||
case "XCODE10-MACOS10.13":
|
|
||||||
return "Hosted macOS High Sierra";
|
|
||||||
case "MACOS 10.14":
|
|
||||||
case "MACOS-10.14":
|
|
||||||
case "MACOS LATEST":
|
|
||||||
case "MACOS-LATEST":
|
|
||||||
return "Hosted macOS";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PipelineBuildContexts have build options.
|
|
||||||
/// GraphExecutionContexts have dependencies.
|
|
||||||
/// We might need either depending on the situation.
|
|
||||||
/// </summary>
|
|
||||||
private TaskAgentPoolReference ValidateQueue(
|
|
||||||
IPipelineContext context,
|
|
||||||
ValidationResult result,
|
|
||||||
BuildOptions buildOptions)
|
|
||||||
{
|
|
||||||
var queueId = 0;
|
|
||||||
var queueName = (String)null;
|
|
||||||
var queueNameIsUnresolvableExpression = false; // true iff Name is an expression, we're allowed to use them, and it has no current value
|
|
||||||
var queue = this.Queue;
|
|
||||||
if (queue != null)
|
|
||||||
{
|
|
||||||
queueId = queue.Id;
|
|
||||||
|
|
||||||
// resolve name
|
|
||||||
var expressionValueName = queue.Name;
|
|
||||||
if (expressionValueName != null && (buildOptions.EnableResourceExpressions || expressionValueName.IsLiteral))
|
|
||||||
{
|
|
||||||
// resolve expression
|
|
||||||
try
|
|
||||||
{
|
|
||||||
queueName = expressionValueName.GetValue(context).Value;
|
|
||||||
queueNameIsUnresolvableExpression = !expressionValueName.IsLiteral && String.IsNullOrEmpty(queueName);
|
|
||||||
}
|
|
||||||
catch (Exception ee)
|
|
||||||
{
|
|
||||||
// something bad happened trying to fetch the value.
|
|
||||||
// We do not really care what though. Just record the error and move on.
|
|
||||||
queueName = null;
|
|
||||||
|
|
||||||
if (buildOptions.ValidateExpressions && buildOptions.ValidateResources)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(ee.Message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve name macro
|
|
||||||
if (buildOptions.EnableResourceExpressions && queueName != null && VariableUtility.IsVariable(queueName))
|
|
||||||
{
|
|
||||||
queueName = context.ExpandVariables(queueName);
|
|
||||||
if (VariableUtility.IsVariable(queueName))
|
|
||||||
{
|
|
||||||
// name appears to be a macro that is not defined.
|
|
||||||
queueNameIsUnresolvableExpression = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queueNameIsUnresolvableExpression || (queueId == 0 && String.IsNullOrEmpty(queueName)))
|
|
||||||
{
|
|
||||||
// could not determine what queue user was talking about
|
|
||||||
if (!buildOptions.AllowEmptyQueueTarget && buildOptions.ValidateResources)
|
|
||||||
{
|
|
||||||
// expression-based queue names are allowed to be unresolved at compile time.
|
|
||||||
// TEMPORARY: literal queue names do not error at compile time if special keys exist
|
|
||||||
if (!queueNameIsUnresolvableExpression || buildOptions.ValidateExpressions)
|
|
||||||
{
|
|
||||||
if (!String.IsNullOrEmpty(queueName))
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFoundByName(queueName)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var expressionValueName = queue?.Name;
|
|
||||||
if (expressionValueName == null || expressionValueName.IsLiteral)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotDefined()));
|
|
||||||
}
|
|
||||||
else if (expressionValueName != null)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFoundByName(expressionValueName.Expression)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// we have a valid queue. record the reference
|
|
||||||
result.AddQueueReference(id: queueId, name: queueName);
|
|
||||||
|
|
||||||
// Attempt to resolve the queue using any identifier specified. We will look up by either ID
|
|
||||||
// or name and the ID is preferred since it is immutable and more specific.
|
|
||||||
if (buildOptions.ValidateResources)
|
|
||||||
{
|
|
||||||
TaskAgentQueue taskAgentQueue = null;
|
|
||||||
var resourceStore = context.ResourceStore;
|
|
||||||
if (resourceStore != null)
|
|
||||||
{
|
|
||||||
if (queueId != 0)
|
|
||||||
{
|
|
||||||
taskAgentQueue = resourceStore.GetQueue(queueId);
|
|
||||||
if (taskAgentQueue == null)
|
|
||||||
{
|
|
||||||
result.UnauthorizedResources.Queues.Add(new AgentQueueReference { Id = queueId });
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFound(queueId)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!String.IsNullOrEmpty(queueName))
|
|
||||||
{
|
|
||||||
taskAgentQueue = resourceStore.GetQueue(queueName);
|
|
||||||
if (taskAgentQueue == null)
|
|
||||||
{
|
|
||||||
result.UnauthorizedResources.Queues.Add(new AgentQueueReference { Name = queueName });
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.QueueNotFoundByName(queueName)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the resolved values inline to the resolved resource for this validation run
|
|
||||||
if (taskAgentQueue != null)
|
|
||||||
{
|
|
||||||
this.Queue.Id = taskAgentQueue.Id;
|
|
||||||
return taskAgentQueue.Pool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Validate(
|
|
||||||
IPipelineContext context,
|
|
||||||
BuildOptions buildOptions,
|
|
||||||
ValidationResult result,
|
|
||||||
IList<Step> steps,
|
|
||||||
ISet<Demand> taskDemands)
|
|
||||||
{
|
|
||||||
// validate queue
|
|
||||||
var resolvedPool = ValidateQueue(context, result, buildOptions);
|
|
||||||
Boolean includeTaskDemands = resolvedPool == null || !resolvedPool.IsHosted;
|
|
||||||
|
|
||||||
// Add advanced-checkout min agent demand
|
|
||||||
Boolean advancedCheckout = false;
|
|
||||||
int checkoutTasks = 0;
|
|
||||||
int injectedSystemTasks = 0;
|
|
||||||
bool countInjectSystemTasks = true;
|
|
||||||
for (int index = 0; index < steps.Count; index++)
|
|
||||||
{
|
|
||||||
var step = steps[index];
|
|
||||||
// Task
|
|
||||||
if (step.Type == StepType.Task)
|
|
||||||
{
|
|
||||||
var task = step as TaskStep;
|
|
||||||
if (task.Name.StartsWith("__system_"))
|
|
||||||
{
|
|
||||||
if (countInjectSystemTasks)
|
|
||||||
{
|
|
||||||
injectedSystemTasks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (task.IsCheckoutTask())
|
|
||||||
{
|
|
||||||
countInjectSystemTasks = false;
|
|
||||||
checkoutTasks++;
|
|
||||||
if (context.EnvironmentVersion < 2)
|
|
||||||
{
|
|
||||||
if (index > 0 && index - injectedSystemTasks > 0)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.CheckoutMustBeTheFirstStep()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
advancedCheckout = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task.Inputs.TryGetValue(PipelineConstants.CheckoutTaskInputs.Repository, out String repository) &&
|
|
||||||
!String.Equals(repository, PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!String.Equals(repository, PipelineConstants.NoneAlias, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!String.Equals(repository, PipelineConstants.DesignerRepo, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.CheckoutStepRepositoryNotSupported(task.Inputs[PipelineConstants.CheckoutTaskInputs.Repository])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
countInjectSystemTasks = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkoutTasks > 1)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.CheckoutMultipleRepositoryNotSupported()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (advancedCheckout)
|
|
||||||
{
|
|
||||||
taskDemands.Add(new DemandMinimumVersion(PipelineConstants.AgentVersionDemandName, PipelineConstants.AdvancedCheckoutMinAgentVersion));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we need to ensure we have only a single demand for the mimimum agent version. We effectively remove
|
|
||||||
// every agent version demand we find and keep track of the one with the highest value. Assuming we located
|
|
||||||
// one or more of these demands we will ensure it is merged in at the end.
|
|
||||||
var minimumAgentVersionDemand = ResolveAgentVersionDemand(taskDemands);
|
|
||||||
minimumAgentVersionDemand = ResolveAgentVersionDemand(this.Demands, minimumAgentVersionDemand);
|
|
||||||
|
|
||||||
// not include demands from task if phase is running inside container
|
|
||||||
// container suppose provide any required tool task needs
|
|
||||||
if (this.Container != null)
|
|
||||||
{
|
|
||||||
includeTaskDemands = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the phase demands with the implicit demands from tasks.
|
|
||||||
if (includeTaskDemands && buildOptions.RollupStepDemands)
|
|
||||||
{
|
|
||||||
this.Demands.UnionWith(taskDemands);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we resolved a minimum agent version demand then we go ahead and merge it in
|
|
||||||
// We want to do this even if targetting Hosted
|
|
||||||
if (minimumAgentVersionDemand != null)
|
|
||||||
{
|
|
||||||
this.Demands.Add(minimumAgentVersionDemand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DemandMinimumVersion ResolveAgentVersionDemand(
|
|
||||||
ISet<Demand> demands,
|
|
||||||
DemandMinimumVersion currentMinimumVersion = null)
|
|
||||||
{
|
|
||||||
var minVersionDemand = DemandMinimumVersion.MaxAndRemove(demands);
|
|
||||||
if (minVersionDemand != null && (currentMinimumVersion == null || DemandMinimumVersion.CompareVersion(minVersionDemand.Value, currentMinimumVersion.Value) > 0))
|
|
||||||
{
|
|
||||||
return minVersionDemand;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return currentMinimumVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override JobExecutionContext CreateJobContext(
|
|
||||||
PhaseExecutionContext context,
|
|
||||||
String jobName,
|
|
||||||
Int32 attempt,
|
|
||||||
Boolean continueOnError,
|
|
||||||
Int32 timeoutInMinutes,
|
|
||||||
Int32 cancelTimeoutInMinutes,
|
|
||||||
IJobFactory jobFactory)
|
|
||||||
{
|
|
||||||
context.Trace?.EnterProperty("CreateJobContext");
|
|
||||||
var execution = this.Execution ?? new ParallelExecutionOptions();
|
|
||||||
var jobContext = execution.CreateJobContext(
|
|
||||||
context,
|
|
||||||
jobName,
|
|
||||||
attempt,
|
|
||||||
this.Container,
|
|
||||||
this.SidecarContainers,
|
|
||||||
continueOnError,
|
|
||||||
timeoutInMinutes,
|
|
||||||
cancelTimeoutInMinutes,
|
|
||||||
jobFactory);
|
|
||||||
context.Trace?.LeaveProperty("CreateJobContext");
|
|
||||||
|
|
||||||
if (jobContext != null)
|
|
||||||
{
|
|
||||||
jobContext.Job.Definition.Workspace = this.Workspace?.Clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
return jobContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override ExpandPhaseResult Expand(
|
|
||||||
PhaseExecutionContext context,
|
|
||||||
Boolean continueOnError,
|
|
||||||
Int32 timeoutInMinutes,
|
|
||||||
Int32 cancelTimeoutInMinutes,
|
|
||||||
IJobFactory jobFactory,
|
|
||||||
JobExpansionOptions options)
|
|
||||||
{
|
|
||||||
context.Trace?.EnterProperty("Expand");
|
|
||||||
var execution = this.Execution ?? new ParallelExecutionOptions();
|
|
||||||
var result = execution.Expand(
|
|
||||||
context,
|
|
||||||
this.Container,
|
|
||||||
this.SidecarContainers,
|
|
||||||
continueOnError,
|
|
||||||
timeoutInMinutes,
|
|
||||||
cancelTimeoutInMinutes,
|
|
||||||
jobFactory,
|
|
||||||
options);
|
|
||||||
context.Trace?.LeaveProperty("Expand");
|
|
||||||
|
|
||||||
foreach (var job in result.Jobs)
|
|
||||||
{
|
|
||||||
job.Definition.Workspace = this.Workspace?.Clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[OnSerializing]
|
|
||||||
private void OnSerializing(StreamingContext context)
|
|
||||||
{
|
|
||||||
if (m_sidecarContainers?.Count == 0)
|
|
||||||
{
|
|
||||||
m_sidecarContainers = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "SidecarContainers", EmitDefaultValue = false)]
|
|
||||||
private IDictionary<String, ExpressionValue<String>> m_sidecarContainers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures conversion of a TaskAgentQueue into an AgentQueueReference works properly when the serializer
|
|
||||||
/// is configured to write/honor type information. This is a temporary converter that may be removed after
|
|
||||||
/// M127 ships.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class QueueJsonConverter : VssSecureJsonConverter
|
|
||||||
{
|
|
||||||
public override Boolean CanWrite => false;
|
|
||||||
|
|
||||||
public override Boolean CanConvert(Type objectType)
|
|
||||||
{
|
|
||||||
return objectType.Equals(typeof(AgentQueueReference));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Object ReadJson(
|
|
||||||
JsonReader reader,
|
|
||||||
Type objectType,
|
|
||||||
Object existingValue,
|
|
||||||
JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var rawValue = JObject.Load(reader);
|
|
||||||
using (var objectReader = rawValue.CreateReader())
|
|
||||||
{
|
|
||||||
var newValue = new AgentQueueReference();
|
|
||||||
serializer.Populate(objectReader, newValue);
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteJson(
|
|
||||||
JsonWriter writer,
|
|
||||||
Object value,
|
|
||||||
JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Artifacts
|
|
||||||
{
|
|
||||||
public static class ArtifactConstants
|
|
||||||
{
|
|
||||||
internal static class ArtifactType
|
|
||||||
{
|
|
||||||
internal const String Build = nameof(Build);
|
|
||||||
internal const String Container = nameof(Container);
|
|
||||||
internal const String Package = nameof(Package);
|
|
||||||
internal const String SourceControl = nameof(SourceControl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Artifacts;
|
|
||||||
namespace GitHub.DistributedTask.Orchestration.Server.Artifacts
|
|
||||||
{
|
|
||||||
public static class DownloadStepExtensions
|
|
||||||
{
|
|
||||||
public static Boolean IsDownloadBuildStepExists(this IReadOnlyList<JobStep> steps)
|
|
||||||
{
|
|
||||||
foreach (var step in steps)
|
|
||||||
{
|
|
||||||
if (step is TaskStep taskStep)
|
|
||||||
{
|
|
||||||
if (taskStep.IsDownloadBuildTask())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadBuildTask(this Step step)
|
|
||||||
{
|
|
||||||
if (step is TaskStep taskStep &&
|
|
||||||
taskStep.Reference != null &&
|
|
||||||
taskStep.Reference.Name.Equals(YamlArtifactConstants.DownloadBuild, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadStepDisabled(this Step step)
|
|
||||||
{
|
|
||||||
// either download task or downloadBuild task has none keyword return true.
|
|
||||||
if (step is TaskStep taskStep &&
|
|
||||||
taskStep.Inputs.TryGetValue(PipelineArtifactConstants.DownloadTaskInputs.Alias, out String alias) &&
|
|
||||||
String.Equals(alias, YamlArtifactConstants.None, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
(step.IsDownloadBuildTask() || step.IsDownloadTask()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadTask(this Step step)
|
|
||||||
{
|
|
||||||
if (step is TaskStep taskStep &&
|
|
||||||
taskStep.Reference != null &&
|
|
||||||
taskStep.Reference.Id.Equals(PipelineArtifactConstants.DownloadTask.Id) &&
|
|
||||||
taskStep.Reference.Version == PipelineArtifactConstants.DownloadTask.Version)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadCurrentPipelineArtifactStep(this Step step)
|
|
||||||
{
|
|
||||||
if (step is TaskStep taskStep &&
|
|
||||||
taskStep.IsDownloadTask() &&
|
|
||||||
taskStep.Inputs.TryGetValue(PipelineArtifactConstants.DownloadTaskInputs.Alias, out String alias) &&
|
|
||||||
String.Equals(alias, YamlArtifactConstants.Current, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadPipelineArtifactStepDisabled(this TaskStep step)
|
|
||||||
{
|
|
||||||
if (step.IsDownloadTask() &&
|
|
||||||
step.Inputs.TryGetValue(PipelineArtifactConstants.DownloadTaskInputs.Alias, out String alias) &&
|
|
||||||
String.Equals(alias, YamlArtifactConstants.None, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadExternalPipelineArtifactStep(this TaskStep step)
|
|
||||||
{
|
|
||||||
if (step.IsDownloadTask() &&
|
|
||||||
step.Inputs != null &&
|
|
||||||
step.Inputs.TryGetValue(PipelineArtifactConstants.DownloadTaskInputs.Alias, out String alias) &&
|
|
||||||
!String.IsNullOrEmpty(alias) &&
|
|
||||||
!alias.Equals(YamlArtifactConstants.Current, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!alias.Equals(YamlArtifactConstants.None, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String GetAliasFromTaskStep(this TaskStep step)
|
|
||||||
{
|
|
||||||
return step.Inputs.TryGetValue(PipelineArtifactConstants.DownloadTaskInputs.Alias, out String alias)
|
|
||||||
? alias
|
|
||||||
: String.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean IsDownloadPipelineArtifactStepExists(this IReadOnlyList<JobStep> steps)
|
|
||||||
{
|
|
||||||
foreach (var step in steps)
|
|
||||||
{
|
|
||||||
if (step is TaskStep taskStep)
|
|
||||||
{
|
|
||||||
if (taskStep.IsDownloadTask())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Merge(
|
|
||||||
this IDictionary<String, String> first,
|
|
||||||
IDictionary<String, String> second)
|
|
||||||
{
|
|
||||||
foreach (var key in second?.Keys ?? new List<String>())
|
|
||||||
{
|
|
||||||
first[key] = second[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Merge(
|
|
||||||
this IDictionary<String, String> first,
|
|
||||||
IReadOnlyDictionary<String, String> second)
|
|
||||||
{
|
|
||||||
foreach (var key in second?.Keys ?? new List<String>())
|
|
||||||
{
|
|
||||||
first[key] = second[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Artifacts
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a mechanism to resolve the artifacts
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public interface IArtifactResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Given a resource, it gets the corresponding task id from its extension
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resource"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Guid GetArtifactDownloadTaskId(Resource resource);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Given a resource and step, it maps the resource properties to task inputs
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resource"></param>
|
|
||||||
/// <param name="taskStep"></param>
|
|
||||||
void PopulateMappedTaskInputs(Resource resource, TaskStep taskStep);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Given an artifact step, it resolves the artifact and returns a download artifact task
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pipelineContext"></param>
|
|
||||||
/// <param name="step"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Boolean ResolveStep(IPipelineContext pipelineContext, JobStep step, out IList<TaskStep> resolvedSteps);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Given resource store and task step it translate the taskStep into actual task reference with mapped inputs
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resourceStore"></param>
|
|
||||||
/// <param name="taskStep"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Boolean ResolveStep(IResourceStore resourceStore, TaskStep taskStep, out String errorMessage);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate the given resource in the YAML file. Also resolve version for the resource if not resolved already
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resources"></param>
|
|
||||||
Boolean ValidateDeclaredResource(Resource resource, out PipelineValidationError error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Artifacts
|
|
||||||
{
|
|
||||||
public static class PipelineArtifactConstants
|
|
||||||
{
|
|
||||||
internal static class CommonArtifactTaskInputValues
|
|
||||||
{
|
|
||||||
internal const String DefaultDownloadPath = "$(Pipeline.Workspace)";
|
|
||||||
internal const String DefaultDownloadPattern = "**";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PipelineArtifactTaskInputs
|
|
||||||
{
|
|
||||||
public const String ArtifactName = "artifactName";
|
|
||||||
|
|
||||||
public const String BuildType = "buildType";
|
|
||||||
|
|
||||||
public const String BuildId = "buildId";
|
|
||||||
|
|
||||||
public const String BuildVersionToDownload = "buildVersionToDownload";
|
|
||||||
|
|
||||||
public const String Definition = "definition";
|
|
||||||
|
|
||||||
public const String DownloadType = "downloadType";
|
|
||||||
|
|
||||||
public const String DownloadPath = "downloadPath";
|
|
||||||
|
|
||||||
public const String FileSharePath = "fileSharePath";
|
|
||||||
|
|
||||||
public const String ItemPattern = "itemPattern";
|
|
||||||
|
|
||||||
public const String Project = "project";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PipelineArtifactTaskInputValues
|
|
||||||
{
|
|
||||||
public const String DownloadTypeSingle = "single";
|
|
||||||
public const String SpecificBuildType = "specific";
|
|
||||||
public const String CurrentBuildType = "current";
|
|
||||||
public const String AutomaticMode = "automatic";
|
|
||||||
public const String ManualMode = "manual";
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class YamlConstants
|
|
||||||
{
|
|
||||||
internal const String Connection = "connection";
|
|
||||||
internal const String Current = "current";
|
|
||||||
internal const String None = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ArtifactTypes
|
|
||||||
{
|
|
||||||
public const string AzurePipelineArtifactType = "Pipeline";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DownloadTaskInputs
|
|
||||||
{
|
|
||||||
public const String Alias = "alias";
|
|
||||||
public const String Artifact = "artifact";
|
|
||||||
public const String Mode = "mode";
|
|
||||||
public const String Path = "path";
|
|
||||||
public const String Patterns = "patterns";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TraceConstants
|
|
||||||
{
|
|
||||||
public const String Area = "PipelineArtifacts";
|
|
||||||
public const String DownloadPipelineArtifactFeature = "DownloadPipelineArtifact";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly TaskDefinition DownloadTask = new TaskDefinition
|
|
||||||
{
|
|
||||||
Id = new Guid("30f35852-3f7e-4c0c-9a88-e127b4f97211"),
|
|
||||||
Name = "Download",
|
|
||||||
FriendlyName = "Download Artifact",
|
|
||||||
Author = "Microsoft",
|
|
||||||
RunsOn = { TaskRunsOnConstants.RunsOnAgent },
|
|
||||||
Version = new TaskVersion("1.0.0"),
|
|
||||||
Description = "Downloads pipeline type artifacts.",
|
|
||||||
HelpMarkDown = "[More Information](https://github.com)",
|
|
||||||
Inputs = {
|
|
||||||
new TaskInputDefinition()
|
|
||||||
{
|
|
||||||
Name = DownloadTaskInputs.Artifact,
|
|
||||||
Required = true,
|
|
||||||
InputType = TaskInputType.String
|
|
||||||
},
|
|
||||||
new TaskInputDefinition()
|
|
||||||
{
|
|
||||||
Name = DownloadTaskInputs.Patterns,
|
|
||||||
Required = false,
|
|
||||||
DefaultValue = "**",
|
|
||||||
InputType = TaskInputType.String
|
|
||||||
},
|
|
||||||
new TaskInputDefinition()
|
|
||||||
{
|
|
||||||
Name = DownloadTaskInputs.Path,
|
|
||||||
Required = false,
|
|
||||||
InputType = TaskInputType.String
|
|
||||||
},
|
|
||||||
new TaskInputDefinition()
|
|
||||||
{
|
|
||||||
Name=DownloadTaskInputs.Alias,
|
|
||||||
Required = false,
|
|
||||||
InputType = TaskInputType.String
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Artifacts
|
|
||||||
{
|
|
||||||
public static class YamlArtifactConstants
|
|
||||||
{
|
|
||||||
public const String Alias = "alias";
|
|
||||||
public const String Connection = "connection";
|
|
||||||
public const String Current = "current";
|
|
||||||
public const String Download = "download";
|
|
||||||
public const String DownloadBuild = "downloadBuild";
|
|
||||||
public const String None = "none";
|
|
||||||
public const String Path = "path";
|
|
||||||
public const String Patterns = "patterns";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a mechanism for controlling validation behaviors.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class BuildOptions
|
|
||||||
{
|
|
||||||
public static BuildOptions None { get; } = new BuildOptions();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not a queue target without a queue should be considered an
|
|
||||||
/// error.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean AllowEmptyQueueTarget
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Allow hyphens in names checked by the NameValidator. Used for yaml workflow schema
|
|
||||||
/// </summary>
|
|
||||||
public Boolean AllowHyphenNames
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether to demand the latest agent version.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean DemandLatestAgent
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If true, resource definitions are allowed to use expressions
|
|
||||||
/// </summary>
|
|
||||||
public Boolean EnableResourceExpressions
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not to resolve resource version.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ResolveResourceVersions
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether input aliases defined in a task definition are honored.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ResolveTaskInputAliases
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the individual step demands should be rolled up into their
|
|
||||||
/// parent phase's demands. Settings this value to true will result in Phase's demand sets being a superset
|
|
||||||
/// of their children's demands.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean RollupStepDemands
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If true, all expressions must be resolvable given a provided context.
|
|
||||||
/// This is normally going to be false for plan compile time and true for plan runtime.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ValidateExpressions
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not to validate resource existence and other constraints.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ValidateResources
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not step names provided by the caller should be validated for
|
|
||||||
/// correctness and uniqueness. Setting this value to false will automatically fix invalid step names and
|
|
||||||
/// de-duplicate step names which may lead to unexpected behavior at runtime when binding output variables.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ValidateStepNames
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not to run input validation defined by the task author.
|
|
||||||
/// </summary>
|
|
||||||
public Boolean ValidateTaskInputs
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public static class BuildPropertyNames
|
|
||||||
{
|
|
||||||
public static readonly String Branch = "branch";
|
|
||||||
public static readonly String Connection = "connection";
|
|
||||||
public static readonly String Source = "source";
|
|
||||||
public static readonly String Type = "type";
|
|
||||||
public static readonly String Version = "version";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a data contract for a build resource referenced by a pipeline.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class BuildResource : Resource
|
|
||||||
{
|
|
||||||
public BuildResource()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BuildResource(BuildResource resourceToCopy)
|
|
||||||
: base(resourceToCopy)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the type of build resource.
|
|
||||||
/// </summary>
|
|
||||||
public String Type
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.Properties.Get<String>(BuildPropertyNames.Type);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.Properties.Set(BuildPropertyNames.Type, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the version of the build resource.
|
|
||||||
/// </summary>
|
|
||||||
public String Version
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.Properties.Get<String>(BuildPropertyNames.Version);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.Properties.Set(BuildPropertyNames.Version, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BuildResource Clone()
|
|
||||||
{
|
|
||||||
return new BuildResource(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.Services.WebApi.Internal;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Checkpoints
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class CheckpointContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unique id of the checkpoint, also used as the timeline record id
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auth token for querying DistributedTask
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public String Token { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checkpoint Instance Id
|
|
||||||
/// Use this for sending decision events and tracing telemetry.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public String OrchestrationId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PlanId
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public Guid PlanId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Which TaskHub to use when sending decision events;
|
|
||||||
/// Use this for sending decision events.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public String HubName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The project requesting decision.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public CheckpointScope Project { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The pipeline (definition) requesting decision.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public PipelineScope Pipeline { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The graph node requesting decision.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public GraphNodeScope GraphNode { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.Services.WebApi.Internal;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Checkpoints
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class CheckpointDecision
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Checkpoint id, provided on context
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decision
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public String Result { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Additional information (optional)
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = false, EmitDefaultValue = false)]
|
|
||||||
public String Message { get; set; }
|
|
||||||
|
|
||||||
// Decision possibilities
|
|
||||||
public const String Approved = "Approved";
|
|
||||||
public const String Denied = "Denied";
|
|
||||||
public const String Canceled = "Canceled";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.WebApi.Internal;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Checkpoints
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides context regarding the state of the orchestration.
|
|
||||||
/// Consumers may choose to use this information to cache decisions.
|
|
||||||
/// EG, if you wanted to return the same decision for this and all
|
|
||||||
/// future requests issuing from the same project / pipeline / stage / run
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class CheckpointScope
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// May be used in uniquely identify this scope for future reference.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public String Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The friendly name of the scope
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public String Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class GraphNodeScope : CheckpointScope
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Facilitates approving only a single attempt of a graph node in a specific run of a pipeline.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public Int32 Attempt { get; set; } = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class PipelineScope : CheckpointScope
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pipeline URLs
|
|
||||||
/// </summary>
|
|
||||||
[DataMember(IsRequired = true)]
|
|
||||||
public TaskOrchestrationOwner Owner { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.Services.WebApi.Internal;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.Checkpoints
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[DataContract]
|
|
||||||
[ClientIgnore]
|
|
||||||
public class ResourceInfo
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public String Id { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public String Name { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public String TypeName { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class ContinuousIntegrationTrigger : PipelineTrigger
|
|
||||||
{
|
|
||||||
public ContinuousIntegrationTrigger()
|
|
||||||
: base(PipelineTriggerType.ContinuousIntegration)
|
|
||||||
{
|
|
||||||
Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = true)]
|
|
||||||
public Boolean Enabled
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether changes should be batched while another CI pipeline is running.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If this is true, then changes submitted while a CI pipeline is running will be batched and built in one new CI pipeline when the current pipeline finishes.
|
|
||||||
/// If this is false, then a new CI pipeline will be triggered for each change to the repository.
|
|
||||||
/// </remarks>
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Boolean BatchChanges
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of filters that describe which branches will trigger pipelines.
|
|
||||||
/// </summary>
|
|
||||||
public IList<String> BranchFilters
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_branchFilters == null)
|
|
||||||
{
|
|
||||||
m_branchFilters = new List<String>();
|
|
||||||
}
|
|
||||||
return m_branchFilters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of filters that describe which paths will trigger pipelines.
|
|
||||||
/// </summary>
|
|
||||||
public IList<String> PathFilters
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_pathFilters == null)
|
|
||||||
{
|
|
||||||
m_pathFilters = new List<String>();
|
|
||||||
}
|
|
||||||
return m_pathFilters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[OnSerializing]
|
|
||||||
private void OnSerializing(StreamingContext context)
|
|
||||||
{
|
|
||||||
if (m_branchFilters?.Count == 0)
|
|
||||||
{
|
|
||||||
m_branchFilters = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_pathFilters?.Count == 0)
|
|
||||||
{
|
|
||||||
m_pathFilters = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "BranchFilters", EmitDefaultValue = false)]
|
|
||||||
private List<String> m_branchFilters;
|
|
||||||
|
|
||||||
[DataMember(Name = "PathFilters", EmitDefaultValue = false)]
|
|
||||||
private List<String> m_pathFilters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a default implementation of a counter store.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class CounterStore : ICounterStore
|
|
||||||
{
|
|
||||||
public CounterStore(
|
|
||||||
IDictionary<String, Int32> counters = null,
|
|
||||||
ICounterResolver resolver = null)
|
|
||||||
{
|
|
||||||
if (counters?.Count > 0)
|
|
||||||
{
|
|
||||||
m_counters.AddRange(counters);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Resolver = resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyDictionary<String, Int32> Counters
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_counters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ICounterResolver Resolver
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Int32 Increment(
|
|
||||||
IPipelineContext context,
|
|
||||||
String prefix,
|
|
||||||
Int32 seed)
|
|
||||||
{
|
|
||||||
if (m_counters.TryGetValue(prefix, out Int32 existingValue))
|
|
||||||
{
|
|
||||||
return existingValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int32 newValue = seed;
|
|
||||||
if (this.Resolver != null)
|
|
||||||
{
|
|
||||||
newValue = this.Resolver.Increment(context, prefix, seed);
|
|
||||||
m_counters[prefix] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<String, Int32> m_counters = new Dictionary<String, Int32>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Runtime;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public struct CreateJobResult
|
|
||||||
{
|
|
||||||
public CreateJobResult(
|
|
||||||
JobExecutionContext context,
|
|
||||||
Job job)
|
|
||||||
{
|
|
||||||
this.Job = job;
|
|
||||||
this.Context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Job Job
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JobExecutionContext Context
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public struct CreateTaskResult
|
|
||||||
{
|
|
||||||
public CreateTaskResult(
|
|
||||||
TaskStep task,
|
|
||||||
TaskDefinition definition)
|
|
||||||
{
|
|
||||||
this.Task = task;
|
|
||||||
this.Definition = definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskStep Task
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskDefinition Definition
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Validation;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
internal enum DeploymentRollingOption
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
Absolute,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
Percentage
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
internal class DeploymentExecutionOptions
|
|
||||||
{
|
|
||||||
public DeploymentExecutionOptions()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeploymentExecutionOptions(DeploymentExecutionOptions optionsToCopy)
|
|
||||||
{
|
|
||||||
this.RollingOption = optionsToCopy.RollingOption;
|
|
||||||
this.RollingValue = optionsToCopy.RollingValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public DeploymentRollingOption RollingOption
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public uint RollingValue
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeploymentExecutionOptions Clone()
|
|
||||||
{
|
|
||||||
return new DeploymentExecutionOptions(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Validate(
|
|
||||||
IPipelineContext context,
|
|
||||||
ValidationResult result)
|
|
||||||
{
|
|
||||||
switch (RollingOption)
|
|
||||||
{
|
|
||||||
case DeploymentRollingOption.Absolute:
|
|
||||||
if (RollingValue == 0)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.InvalidAbsoluteRollingValue()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DeploymentRollingOption.Percentage:
|
|
||||||
if (RollingValue == 0 || RollingValue > 100)
|
|
||||||
{
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.InvalidPercentageRollingValue()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
result.Errors.Add(new PipelineValidationError(PipelineStrings.InvalidRollingOption(RollingOption)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Runtime;
|
|
||||||
using GitHub.DistributedTask.Pipelines.Validation;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
internal class DeploymentGroupTarget : PhaseTarget
|
|
||||||
{
|
|
||||||
public DeploymentGroupTarget()
|
|
||||||
: base(PhaseTargetType.DeploymentGroup)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeploymentGroupTarget(DeploymentGroupTarget targetToClone)
|
|
||||||
: base(targetToClone)
|
|
||||||
{
|
|
||||||
this.DeploymentGroup = targetToClone.DeploymentGroup?.Clone();
|
|
||||||
this.Execution = targetToClone.Execution?.Clone();
|
|
||||||
|
|
||||||
if (targetToClone.m_tags != null && targetToClone.m_tags.Count > 0)
|
|
||||||
{
|
|
||||||
m_tags = new HashSet<String>(targetToClone.m_tags, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public DeploymentGroupReference DeploymentGroup
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISet<String> Tags
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_tags == null)
|
|
||||||
{
|
|
||||||
m_tags = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
return m_tags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets targets Ids filter on which deployment should be done.
|
|
||||||
/// </summary>
|
|
||||||
public List<Int32> TargetIds
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_targetIds == null)
|
|
||||||
{
|
|
||||||
m_targetIds = new List<Int32>();
|
|
||||||
}
|
|
||||||
return m_targetIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DeploymentExecutionOptions Execution
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override PhaseTarget Clone()
|
|
||||||
{
|
|
||||||
return new DeploymentGroupTarget(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean IsValid(TaskDefinition task)
|
|
||||||
{
|
|
||||||
return task.RunsOn.Contains(TaskRunsOnConstants.RunsOnDeploymentGroup, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Validate(
|
|
||||||
IPipelineContext context,
|
|
||||||
BuildOptions buildOptions,
|
|
||||||
ValidationResult result,
|
|
||||||
IList<Step> steps,
|
|
||||||
ISet<Demand> taskDemands)
|
|
||||||
{
|
|
||||||
this.Execution?.Validate(context, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override JobExecutionContext CreateJobContext(
|
|
||||||
PhaseExecutionContext context,
|
|
||||||
String jobName,
|
|
||||||
Int32 attempt,
|
|
||||||
Boolean continueOnError,
|
|
||||||
Int32 timeoutInMinutes,
|
|
||||||
Int32 cancelTimeoutInMinutes,
|
|
||||||
IJobFactory jobFactory)
|
|
||||||
{
|
|
||||||
context.Trace?.EnterProperty("CreateJobContext");
|
|
||||||
var result = new ParallelExecutionOptions().CreateJobContext(
|
|
||||||
context,
|
|
||||||
jobName,
|
|
||||||
attempt,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
continueOnError,
|
|
||||||
timeoutInMinutes,
|
|
||||||
cancelTimeoutInMinutes,
|
|
||||||
jobFactory);
|
|
||||||
context.Trace?.LeaveProperty("CreateJobContext");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override ExpandPhaseResult Expand(
|
|
||||||
PhaseExecutionContext context,
|
|
||||||
Boolean continueOnError,
|
|
||||||
Int32 timeoutInMinutes,
|
|
||||||
Int32 cancelTimeoutInMinutes,
|
|
||||||
IJobFactory jobFactory,
|
|
||||||
JobExpansionOptions options)
|
|
||||||
{
|
|
||||||
context.Trace?.EnterProperty("Expand");
|
|
||||||
var result = new ParallelExecutionOptions().Expand(
|
|
||||||
context: context,
|
|
||||||
container: null,
|
|
||||||
sidecarContainers: null,
|
|
||||||
continueOnError: continueOnError,
|
|
||||||
timeoutInMinutes: timeoutInMinutes,
|
|
||||||
cancelTimeoutInMinutes: cancelTimeoutInMinutes,
|
|
||||||
jobFactory: jobFactory,
|
|
||||||
options: options);
|
|
||||||
context.Trace?.LeaveProperty("Expand");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[OnSerializing]
|
|
||||||
private void OnSerializing(StreamingContext context)
|
|
||||||
{
|
|
||||||
if (m_tags?.Count == 0)
|
|
||||||
{
|
|
||||||
m_tags = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_targetIds?.Count == 0)
|
|
||||||
{
|
|
||||||
m_targetIds = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "Tags", EmitDefaultValue = false)]
|
|
||||||
private ISet<String> m_tags;
|
|
||||||
|
|
||||||
[DataMember(Name = "TargetIds")]
|
|
||||||
private List<Int32> m_targetIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class EnvironmentReference : ResourceReference
|
|
||||||
{
|
|
||||||
public EnvironmentReference()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private EnvironmentReference(EnvironmentReference referenceToCopy)
|
|
||||||
: base(referenceToCopy)
|
|
||||||
{
|
|
||||||
this.Id = referenceToCopy.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public Int32 Id
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EnvironmentReference Clone()
|
|
||||||
{
|
|
||||||
return new EnvironmentReference(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override String ToString()
|
|
||||||
{
|
|
||||||
return base.ToString() ?? this.Id.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public class EnvironmentDeploymentTarget
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public Int32 EnvironmentId { get; set; }
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public String EnvironmentName { get; set; }
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public EnvironmentResourceReference Resource { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user