Compare commits

..

1 Commits

Author SHA1 Message Date
Tingluo Huang
2ca5ee0fbd support defaults. 2020-03-17 22:52:48 -04:00
44 changed files with 706 additions and 994 deletions

View File

@@ -1,24 +1,27 @@
## Features
- Update Runner Register GitHub API URL to Support Org-level Runner (#339 #345 #352)
- Preserve workflow file/line/column for better error messages (#356)
- Switch to use token service instead of SPS for exchanging oauth token. (#325)
- Load and print machine setup info from .setup_info (#364)
- Expose job name as $GITHUB_JOB (#366)
- Add support for job outputs. (#365)
- Set CI=true when launch process in actions runner. (#374)
- Set steps.<id>.outcome and steps.<id>.conclusion. (#372)
- Add support for workflow/job defaults. (#369)
- Expose GITHUB_REPOSITORY_OWNER and ${{github.repository_owner}}. (#378)
- Expose whether debug is on/off via RUNNER_DEBUG. (#253)
- Upload log on runner when worker get killed due to cancellation timeout. (#255)
- Update config.sh/cmd --help documentation (#282)
- Set http_proxy and related env vars for job/service containers (#304)
- Set both http_proxy and HTTP_PROXY env for runner/worker processes. (#298)
## Bugs
- Use authenticate endpoint for testing runner connection. (#311)
- Commands translate file path from container action (#331)
- Change problem matchers output to debug (#363)
- Switch hashFiles to extension function (#362)
- Add expanded volumes strings to container mounts (#384)
- Verify runner Windows service hash started successfully after configuration (#236)
- Detect source file path in L0 without using env. (#257)
- Handle escaped '%' in commands data section (#200)
- Allow container to be null/empty during matrix expansion (#266)
- Translate problem matcher file to host path (#272)
- Change hashFiles() expression function to use @actions/glob. (#268)
- Default post-job action's condition to always(). (#293)
- Support action.yaml file as action's entry file (#288)
- Trace javascript action exit code to debug instead of user logs (#290)
- Change prompt message when removing a runner to lines up with GitHub.com UI (#303)
- Include step.env as part of env context. (#300)
- Update Base64 Encoders to deal with suffixes (#284)
## Misc
- Add runner auth documentation (#357)
- Move .sln file under ./src (#238)
- Treat warnings as errors during compile (#249)
## Windows x64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows

View File

@@ -15,9 +15,6 @@ namespace GitHub.Runner.Common
[DataContract]
public sealed class RunnerSettings
{
[DataMember(Name = "IsHostedServer", EmitDefaultValue = false)]
private bool? _isHostedServer;
[DataMember(EmitDefaultValue = false)]
public int AgentId { get; set; }
@@ -45,21 +42,6 @@ namespace GitHub.Runner.Common
[DataMember(EmitDefaultValue = false)]
public string MonitorSocketAddress { get; set; }
[IgnoreDataMember]
public bool IsHostedServer
{
get
{
// Old runners do not have this property. Hosted runners likely don't have this property either.
return _isHostedServer ?? true;
}
set
{
_isHostedServer = value;
}
}
/// <summary>
// Computed property for convenience. Can either return:
// 1. If runner was configured at the repo level, returns something like: "myorg/myrepo"
@@ -87,15 +69,6 @@ namespace GitHub.Runner.Common
return repoOrOrgName;
}
}
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
if (_isHostedServer.HasValue && _isHostedServer.Value)
{
_isHostedServer = null;
}
}
}
[ServiceLocator(Default = typeof(ConfigurationStore))]

View File

@@ -86,6 +86,7 @@ namespace GitHub.Runner.Listener.Configuration
RunnerSettings runnerSettings = new RunnerSettings();
bool isHostedServer = false;
// Loop getting url and creds until you can connect
ICredentialProvider credProvider = null;
VssCredentials creds = null;
@@ -93,11 +94,9 @@ namespace GitHub.Runner.Listener.Configuration
while (true)
{
// Get the URL
var isGitHub = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("_IS_GITHUB"));
var inputUrl = command.GetUrl();
if (!inputUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase) &&
!inputUrl.Contains("github.localhost", StringComparison.OrdinalIgnoreCase) &&
!isGitHub)
!inputUrl.Contains("github.localhost", StringComparison.OrdinalIgnoreCase))
{
runnerSettings.ServerUrl = inputUrl;
// Get the credentials
@@ -118,7 +117,7 @@ namespace GitHub.Runner.Listener.Configuration
try
{
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
runnerSettings.IsHostedServer = await IsHostedServer(runnerSettings.ServerUrl, creds);
isHostedServer = await IsHostedServer(runnerSettings.ServerUrl, creds);
// Validate can connect.
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
@@ -200,7 +199,7 @@ namespace GitHub.Runner.Listener.Configuration
}
else
{
// Create a new agent.
// Create a new agent.
agent = CreateNewAgent(runnerSettings.AgentName, publicKey);
try
@@ -249,7 +248,7 @@ namespace GitHub.Runner.Listener.Configuration
{
UriBuilder configServerUrl = new UriBuilder(runnerSettings.ServerUrl);
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
if (!runnerSettings.IsHostedServer && Uri.Compare(configServerUrl.Uri, oauthEndpointUrlBuilder.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
if (!isHostedServer && Uri.Compare(configServerUrl.Uri, oauthEndpointUrlBuilder.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
{
oauthEndpointUrlBuilder.Scheme = configServerUrl.Scheme;
oauthEndpointUrlBuilder.Host = configServerUrl.Host;
@@ -292,7 +291,7 @@ namespace GitHub.Runner.Listener.Configuration
{
// there are two exception messages server send that indicate clock skew.
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
Trace.Error("Catch exception during test agent connection.");
Trace.Error(ex);
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
@@ -382,6 +381,7 @@ namespace GitHub.Runner.Listener.Configuration
}
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
bool isHostedServer = await IsHostedServer(settings.ServerUrl, creds);
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName);
@@ -404,7 +404,7 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
}
//delete credential config files
//delete credential config files
currentAction = "Removing .credentials";
if (hasCredentials)
{
@@ -418,7 +418,7 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteLine("Does not exist. Skipping " + currentAction);
}
//delete settings config file
//delete settings config file
currentAction = "Removing .runner";
if (isConfigured)
{
@@ -522,7 +522,7 @@ namespace GitHub.Runner.Listener.Configuration
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
{
var gitHubUrlBuilder = new UriBuilder(githubUrl);
var githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/actions/runner-registration";
var githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler))
{

View File

@@ -58,14 +58,8 @@ namespace GitHub.Runner.Worker
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is depreciated. Please remove it from the repository's secrets");
}
// Clear the cache (for self-hosted runners)
// Note, temporarily avoid this step for the on-premises product, to avoid rate limiting.
var configurationStore = HostContext.GetService<IConfigurationStore>();
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
{
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
}
// Clear the cache (local runner)
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
foreach (var action in actions)
{
@@ -454,8 +448,7 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
string watermarkFile = destDirectory + ".completed";
if (File.Exists(watermarkFile))
if (File.Exists(destDirectory + ".completed"))
{
executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'.");
return;
@@ -505,33 +498,24 @@ namespace GitHub.Runner.Worker
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler))
{
var configurationStore = HostContext.GetService<IConfigurationStore>();
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
if (string.IsNullOrEmpty(authToken))
{
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
if (string.IsNullOrEmpty(authToken))
{
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
}
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
}
if (!string.IsNullOrEmpty(authToken))
{
HostContext.SecretMasker.AddValue(authToken);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
else
{
var accessToken = executionContext.GetGitHubContext("token");
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
if (!string.IsNullOrEmpty(authToken))
{
HostContext.SecretMasker.AddValue(authToken);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
else
{
// Intentionally empty. Temporary for GHES alpha release, download from dotcom unauthenticated.
var accessToken = executionContext.GetGitHubContext("token");
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent);
@@ -626,7 +610,7 @@ namespace GitHub.Runner.Worker
}
Trace.Verbose("Create watermark file indicate action download succeed.");
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
File.WriteAllText(destDirectory + ".completed", DateTime.UtcNow.ToString());
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
Trace.Info("Finished getting action repository.");

View File

@@ -22,11 +22,11 @@ namespace GitHub.Runner.Worker
{
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> contextData);
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> contextData);
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token);
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token, IDictionary<string, PipelineContextData> contextData);
}
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Worker
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
{
var context = CreateContext(executionContext);
var context = CreateContext(executionContext, null);
ActionDefinitionData actionDefinition = new ActionDefinitionData();
try
{
@@ -133,13 +133,13 @@ namespace GitHub.Runner.Worker
public List<string> EvaluateContainerArguments(
IExecutionContext executionContext,
SequenceToken token,
IDictionary<string, PipelineContextData> extraExpressionValues)
IDictionary<string, PipelineContextData> contextData)
{
var result = new List<string>();
if (token != null)
{
var context = CreateContext(executionContext, extraExpressionValues);
var context = CreateContext(executionContext, contextData);
try
{
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true);
@@ -172,13 +172,13 @@ namespace GitHub.Runner.Worker
public Dictionary<string, string> EvaluateContainerEnvironment(
IExecutionContext executionContext,
MappingToken token,
IDictionary<string, PipelineContextData> extraExpressionValues)
IDictionary<string, PipelineContextData> contextData)
{
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (token != null)
{
var context = CreateContext(executionContext, extraExpressionValues);
var context = CreateContext(executionContext, contextData);
try
{
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true);
@@ -216,12 +216,13 @@ namespace GitHub.Runner.Worker
public string EvaluateDefaultInput(
IExecutionContext executionContext,
string inputName,
TemplateToken token)
TemplateToken token,
IDictionary<string, PipelineContextData> contextData)
{
string result = "";
if (token != null)
{
var context = CreateContext(executionContext);
var context = CreateContext(executionContext, contextData);
try
{
var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true);
@@ -246,7 +247,7 @@ namespace GitHub.Runner.Worker
private TemplateContext CreateContext(
IExecutionContext executionContext,
IDictionary<string, PipelineContextData> extraExpressionValues = null)
IDictionary<string, PipelineContextData> contextData)
{
var result = new TemplateContext
{
@@ -260,27 +261,14 @@ namespace GitHub.Runner.Worker
TraceWriter = executionContext.ToTemplateTraceWriter(),
};
// Expression values from execution context
foreach (var pair in executionContext.ExpressionValues)
if (contextData?.Count > 0)
{
result.ExpressionValues[pair.Key] = pair.Value;
}
// Extra expression values
if (extraExpressionValues?.Count > 0)
{
foreach (var pair in extraExpressionValues)
foreach (var pair in contextData)
{
result.ExpressionValues[pair.Key] = pair.Value;
}
}
// Expression functions from execution context
foreach (var item in executionContext.ExpressionFunctions)
{
result.ExpressionFunctions.Add(item);
}
// Add the file table
if (_fileTable?.Count > 0)
{

View File

@@ -26,7 +26,7 @@ namespace GitHub.Runner.Worker
public interface IActionRunner : IStep, IRunnerService
{
ActionRunStage Stage { get; set; }
bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
Boolean TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
Pipelines.ActionStep Action { get; set; }
}
@@ -142,7 +142,7 @@ namespace GitHub.Runner.Worker
// Load the inputs.
ExecutionContext.Debug("Loading inputs");
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues);
foreach (KeyValuePair<string, string> input in inputs)
{
@@ -162,7 +162,13 @@ namespace GitHub.Runner.Worker
string key = input.Key.AssertString("action input name").Value;
if (!inputs.ContainsKey(key))
{
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value);
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
foreach (var data in ExecutionContext.ExpressionValues)
{
evaluateContext[data.Key] = data.Value;
}
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value, evaluateContext);
}
}
}
@@ -287,14 +293,10 @@ namespace GitHub.Runner.Worker
return displayName;
}
// Try evaluating fully
var templateEvaluator = context.ToPipelineTemplateEvaluator();
try
{
if (tokenToParse.CheckHasRequiredContext(contextData, context.ExpressionFunctions))
{
var templateEvaluator = context.ToPipelineTemplateEvaluator();
displayName = templateEvaluator.EvaluateStepDisplayName(tokenToParse, contextData, context.ExpressionFunctions);
didFullyEvaluate = true;
}
didFullyEvaluate = templateEvaluator.TryEvaluateStepDisplayName(tokenToParse, contextData, out displayName);
}
catch (TemplateValidationException e)
{

View File

@@ -61,7 +61,6 @@ namespace GitHub.Runner.Worker.Container
foreach (var volume in container.Volumes)
{
UserMountVolumes[volume] = volume;
MountVolumes.Add(new MountVolume(volume));
}
}

View File

@@ -130,13 +130,6 @@ namespace GitHub.Runner.Worker.Container
// Watermark for GitHub Action environment
dockerOptions.Add("-e GITHUB_ACTIONS=true");
// Set CI=true when no one else already set it.
// CI=true is common set in most CI provider in GitHub
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
{
dockerOptions.Add("-e CI=true");
}
foreach (var volume in container.MountVolumes)
{
// replace `"` with `\"` and add `"{0}"` to all path.
@@ -196,13 +189,6 @@ namespace GitHub.Runner.Worker.Container
// Watermark for GitHub Action environment
dockerOptions.Add("-e GITHUB_ACTIONS=true");
// Set CI=true when no one else already set it.
// CI=true is common set in most CI provider in GitHub
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
{
dockerOptions.Add("-e CI=true");
}
if (!string.IsNullOrEmpty(container.ContainerEntryPoint))
{
dockerOptions.Add($"--entrypoint \"{container.ContainerEntryPoint}\"");

View File

@@ -1,15 +1,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using GitHub.DistributedTask.Expressions2;
using GitHub.Runner.Worker.Container;
using GitHub.Services.WebApi;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
@@ -17,11 +16,12 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using System.Text;
using System.Collections;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Expressions2;
namespace GitHub.Runner.Worker
{
@@ -55,7 +55,6 @@ namespace GitHub.Runner.Worker
IList<String> FileTable { get; }
StepsContext StepsContext { get; }
DictionaryContextData ExpressionValues { get; }
IList<IFunctionInfo> ExpressionFunctions { get; }
List<string> PrependPath { get; }
ContainerInfo Container { get; set; }
List<ContainerInfo> ServiceContainers { get; }
@@ -149,7 +148,6 @@ namespace GitHub.Runner.Worker
public IList<String> FileTable { get; private set; }
public StepsContext StepsContext { get; private set; }
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
public bool WriteDebug { get; private set; }
public List<string> PrependPath { get; private set; }
public ContainerInfo Container { get; set; }
@@ -282,10 +280,6 @@ namespace GitHub.Runner.Worker
{
child.ExpressionValues[pair.Key] = pair.Value;
}
foreach (var item in ExpressionFunctions)
{
child.ExpressionFunctions.Add(item);
}
child._cancellationTokenSource = new CancellationTokenSource();
child.WriteDebug = WriteDebug;
child._parentExecutionContext = this;
@@ -599,6 +593,12 @@ namespace GitHub.Runner.Worker
// File table
FileTable = new List<String>(message.FileTable ?? new string[0]);
// Expression functions
if (Variables.GetBoolean("System.HashFilesV2") == true)
{
ExpressionConstants.UpdateFunction<Handlers.HashFiles>("hashFiles", 1, byte.MaxValue);
}
// Expression values
if (message.ContextData?.Count > 0)
{
@@ -915,19 +915,11 @@ namespace GitHub.Runner.Worker
}
}
public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this IExecutionContext context)
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context)
{
return new[] { new KeyValuePair<string, object>(nameof(IExecutionContext), context) };
}
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
{
if (traceWriter == null)
{
traceWriter = context.ToTemplateTraceWriter();
}
var schema = PipelineTemplateSchemaFactory.GetSchema();
return new PipelineTemplateEvaluator(traceWriter, schema, context.FileTable);
var templateTrace = context.ToTemplateTraceWriter();
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
return new PipelineTemplateEvaluator(templateTrace, schema, context.FileTable);
}
public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context)
@@ -942,7 +934,6 @@ namespace GitHub.Runner.Worker
internal TemplateTraceWriter(IExecutionContext executionContext)
{
ArgUtil.NotNull(executionContext, nameof(executionContext));
_executionContext = executionContext;
}

View File

@@ -8,9 +8,28 @@ using System.Reflection;
using System.Threading;
using System.Collections.Generic;
namespace GitHub.Runner.Worker.Expressions
namespace GitHub.Runner.Worker.Handlers
{
public sealed class HashFilesFunction : Function
public class FunctionTrace : ITraceWriter
{
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
public FunctionTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
{
_trace = trace;
}
public void Info(string message)
{
_trace.Info(message);
}
public void Verbose(string message)
{
_trace.Info(message);
}
}
public sealed class HashFiles : Function
{
protected sealed override Object EvaluateCore(
EvaluationContext context,
@@ -63,7 +82,7 @@ namespace GitHub.Runner.Worker.Expressions
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty;
var p = new ProcessInvoker(new HashFilesTrace(context.Trace));
var p = new ProcessInvoker(new FunctionTrace(context.Trace));
p.ErrorDataReceived += ((_, data) =>
{
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
@@ -103,24 +122,5 @@ namespace GitHub.Runner.Worker.Expressions
return hashResult;
}
private sealed class HashFilesTrace : ITraceWriter
{
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
public HashFilesTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
{
_trace = trace;
}
public void Info(string message)
{
_trace.Info(message);
}
public void Verbose(string message)
{
_trace.Info(message);
}
}
}
}

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(ExpressionManager))]
public interface IExpressionManager : IRunnerService
{
ConditionResult Evaluate(IExecutionContext context, string condition, bool hostTracingOnly = false);
}
public sealed class ExpressionManager : RunnerService, IExpressionManager
{
public ConditionResult Evaluate(IExecutionContext executionContext, string condition, bool hostTracingOnly = false)
{
ArgUtil.NotNull(executionContext, nameof(executionContext));
ConditionResult result = new ConditionResult();
var expressionTrace = new TraceWriter(Trace, hostTracingOnly ? null : executionContext);
var tree = Parse(executionContext, expressionTrace, condition);
var expressionResult = tree.Evaluate(expressionTrace, HostContext.SecretMasker, state: executionContext, options: null);
result.Value = expressionResult.IsTruthy;
result.Trace = expressionTrace.Trace;
return result;
}
private static IExpressionNode Parse(IExecutionContext executionContext, TraceWriter expressionTrace, string condition)
{
ArgUtil.NotNull(executionContext, nameof(executionContext));
if (string.IsNullOrWhiteSpace(condition))
{
condition = $"{PipelineTemplateConstants.Success}()";
}
var parser = new ExpressionParser();
var namedValues = executionContext.ExpressionValues.Keys.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
var functions = new IFunctionInfo[]
{
new FunctionInfo<AlwaysNode>(name: Constants.Expressions.Always, minParameters: 0, maxParameters: 0),
new FunctionInfo<CancelledNode>(name: Constants.Expressions.Cancelled, minParameters: 0, maxParameters: 0),
new FunctionInfo<FailureNode>(name: Constants.Expressions.Failure, minParameters: 0, maxParameters: 0),
new FunctionInfo<SuccessNode>(name: Constants.Expressions.Success, minParameters: 0, maxParameters: 0),
};
return parser.CreateTree(condition, expressionTrace, namedValues, functions) ?? new SuccessNode();
}
private sealed class TraceWriter : DistributedTask.Expressions2.ITraceWriter
{
private readonly IExecutionContext _executionContext;
private readonly Tracing _trace;
private readonly StringBuilder _traceBuilder = new StringBuilder();
public string Trace => _traceBuilder.ToString();
public TraceWriter(Tracing trace, IExecutionContext executionContext)
{
ArgUtil.NotNull(trace, nameof(trace));
_trace = trace;
_executionContext = executionContext;
}
public void Info(string message)
{
_trace.Info(message);
_executionContext?.Debug(message);
_traceBuilder.AppendLine(message);
}
public void Verbose(string message)
{
_trace.Verbose(message);
_executionContext?.Debug(message);
}
}
private sealed class AlwaysNode : Function
{
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
{
resultMemory = null;
return true;
}
}
private sealed class CancelledNode : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var executionContext = evaluationContext.State as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Cancelled;
}
}
private sealed class FailureNode : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var executionContext = evaluationContext.State as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Failure;
}
}
private sealed class SuccessNode : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var executionContext = evaluationContext.State as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Success;
}
}
private sealed class ContextValueNode : NamedValue
{
protected override Object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var jobContext = evaluationContext.State as IExecutionContext;
ArgUtil.NotNull(jobContext, nameof(jobContext));
return jobContext.ExpressionValues[Name];
}
}
}
public class ConditionResult
{
public ConditionResult(bool value = false, string trace = null)
{
this.Value = value;
this.Trace = trace;
}
public bool Value { get; set; }
public string Trace { get; set; }
public static implicit operator ConditionResult(bool value)
{
return new ConditionResult(value);
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker.Expressions
{
public sealed class AlwaysFunction : Function
{
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
{
resultMemory = null;
return true;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker.Expressions
{
public sealed class CancelledFunction : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var templateContext = evaluationContext.State as TemplateContext;
ArgUtil.NotNull(templateContext, nameof(templateContext));
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Cancelled;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker.Expressions
{
public sealed class FailureFunction : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var templateContext = evaluationContext.State as TemplateContext;
ArgUtil.NotNull(templateContext, nameof(templateContext));
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Failure;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker.Expressions
{
public sealed class SuccessFunction : Function
{
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
{
resultMemory = null;
var templateContext = evaluationContext.State as TemplateContext;
ArgUtil.NotNull(templateContext, nameof(templateContext));
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Success;
}
}
}

View File

@@ -10,7 +10,6 @@ namespace GitHub.Runner.Worker
{
"action",
"actor",
"api_url", // temp for GHES alpha release
"base_ref",
"event_name",
"event_path",
@@ -18,11 +17,9 @@ namespace GitHub.Runner.Worker
"job",
"ref",
"repository",
"repository_owner",
"run_id",
"run_number",
"sha",
"url", // temp for GHES alpha release
"workflow",
"workspace",
};

View File

@@ -97,14 +97,14 @@ namespace GitHub.Runner.Worker.Handlers
}
}
var extraExpressionValues = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
extraExpressionValues["inputs"] = inputsContext;
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
evaluateContext["inputs"] = inputsContext;
var manifestManager = HostContext.GetService<IActionManifestManager>();
if (Data.Arguments != null)
{
container.ContainerEntryPointArgs = "";
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, extraExpressionValues);
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, evaluateContext);
foreach (var arg in evaluatedArgs)
{
if (!string.IsNullOrEmpty(arg))
@@ -124,7 +124,7 @@ namespace GitHub.Runner.Worker.Handlers
if (Data.Environment != null)
{
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, extraExpressionValues);
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, evaluateContext);
foreach (var env in evaluatedEnv)
{
if (!this.Environment.ContainsKey(env.Key))

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
@@ -128,23 +127,12 @@ namespace GitHub.Runner.Worker
context.SetRunnerContext("workspace", Path.Combine(_workDirectory, trackingConfig.PipelineDirectory));
context.SetGitHubContext("workspace", Path.Combine(_workDirectory, trackingConfig.WorkspaceDirectory));
// Temporary hack for GHES alpha
var configurationStore = HostContext.GetService<IConfigurationStore>();
var runnerSettings = configurationStore.GetSettings();
if (!runnerSettings.IsHostedServer && !string.IsNullOrEmpty(runnerSettings.GitHubUrl))
{
var url = new Uri(runnerSettings.GitHubUrl);
var portInfo = url.IsDefaultPort ? string.Empty : $":{url.Port.ToString(CultureInfo.InvariantCulture)}";
context.SetGitHubContext("url", $"{url.Scheme}://{url.Host}{portInfo}");
context.SetGitHubContext("api_url", $"{url.Scheme}://api.{url.Host}{portInfo}");
}
// Evaluate the job-level environment variables
context.Debug("Evaluating job-level environment variables");
var templateEvaluator = context.ToPipelineTemplateEvaluator();
foreach (var token in message.EnvironmentVariables)
{
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, jobContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
foreach (var pair in environmentVariables)
{
context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty;
@@ -154,7 +142,7 @@ namespace GitHub.Runner.Worker
// Evaluate the job container
context.Debug("Evaluating job container");
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues);
if (container != null)
{
jobContext.Container = new Container.ContainerInfo(HostContext, container);
@@ -162,7 +150,7 @@ namespace GitHub.Runner.Worker
// Evaluate the job service containers
context.Debug("Evaluating job service containers");
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues);
if (serviceContainers?.Count > 0)
{
foreach (var pair in serviceContainers)
@@ -182,7 +170,7 @@ namespace GitHub.Runner.Worker
{
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues);
foreach (var pair in jobDefaults)
{
if (!string.IsNullOrEmpty(pair.Value))
@@ -349,7 +337,7 @@ namespace GitHub.Runner.Worker
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
var templateEvaluator = context.ToPipelineTemplateEvaluator();
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions);
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues);
foreach (var output in outputs)
{
if (string.IsNullOrEmpty(output.Value))

View File

@@ -1,6 +1,8 @@
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Common.Util;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.Expressions2;
@@ -8,13 +10,8 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Expressions;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker
{
@@ -66,7 +63,11 @@ namespace GitHub.Runner.Worker
}
var step = jobContext.JobSteps.Dequeue();
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null;
IStep nextStep = null;
if (jobContext.JobSteps.Count > 0)
{
nextStep = jobContext.JobSteps.Peek();
}
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
@@ -75,13 +76,6 @@ namespace GitHub.Runner.Worker
// Start
step.ExecutionContext.Start();
// Expression functions
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0));
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0));
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0));
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
// Initialize scope
if (InitializeScope(step, scopeInputs))
{
@@ -105,13 +99,14 @@ namespace GitHub.Runner.Worker
// Evaluate and merge action's env block to env context
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
foreach (var env in actionEnvironment)
{
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
}
}
var expressionManager = HostContext.GetService<IExpressionManager>();
try
{
// Register job cancellation call back only if job cancellation token not been fire before each step run
@@ -125,29 +120,28 @@ namespace GitHub.Runner.Worker
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
var conditionReTestResult = false;
ConditionResult conditionReTestResult;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
conditionReTestResult = false;
}
else
{
try
{
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
var condition = new BasicExpressionToken(null, null, null, step.Condition);
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true);
}
catch (Exception ex)
{
// Cancel the step since we get exception while re-evaluate step condition.
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
step.ExecutionContext.Error(ex);
conditionReTestResult = false;
}
}
if (!conditionReTestResult)
if (!conditionReTestResult.Value)
{
// Cancel the step.
Trace.Info("Cancel current running step.");
@@ -167,35 +161,34 @@ namespace GitHub.Runner.Worker
// Evaluate condition.
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
var conditionResult = false;
var conditionEvaluateError = default(Exception);
Exception conditionEvaluateError = null;
ConditionResult conditionResult;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
conditionResult = false;
}
else
{
try
{
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
var condition = new BasicExpressionToken(null, null, null, step.Condition);
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition);
}
catch (Exception ex)
{
Trace.Info("Caught exception from expression.");
Trace.Error(ex);
conditionResult = false;
conditionEvaluateError = ex;
}
}
// no evaluate error but condition is false
if (!conditionResult && conditionEvaluateError == null)
if (!conditionResult.Value && conditionEvaluateError == null)
{
// Condition == false
Trace.Info("Skipping step due to condition evaluation.");
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace);
}
else if (conditionEvaluateError != null)
{
@@ -255,7 +248,7 @@ namespace GitHub.Runner.Worker
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
try
{
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues);
}
catch (Exception ex)
{
@@ -346,7 +339,7 @@ namespace GitHub.Runner.Worker
var continueOnError = false;
try
{
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues);
}
catch (Exception ex)
{
@@ -399,7 +392,7 @@ namespace GitHub.Runner.Worker
var inputs = default(DictionaryContextData);
try
{
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues);
}
catch (Exception ex)
{
@@ -455,7 +448,7 @@ namespace GitHub.Runner.Worker
var outputs = default(DictionaryContextData);
try
{
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues);
}
catch (Exception ex)
{
@@ -483,43 +476,5 @@ namespace GitHub.Runner.Worker
executionContext.Complete(result, resultCode: resultCode);
}
private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
{
private readonly IExecutionContext _executionContext;
private readonly Tracing _trace;
private readonly StringBuilder _traceBuilder = new StringBuilder();
public string Trace => _traceBuilder.ToString();
public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext)
{
ArgUtil.NotNull(trace, nameof(trace));
_trace = trace;
_executionContext = executionContext;
}
public void Error(string format, params Object[] args)
{
var message = StringUtil.Format(format, args);
_trace.Error(message);
_executionContext?.Debug(message);
}
public void Info(string format, params Object[] args)
{
var message = StringUtil.Format(format, args);
_trace.Info(message);
_executionContext?.Debug(message);
_traceBuilder.AppendLine(message);
}
public void Verbose(string format, params Object[] args)
{
var message = StringUtil.Format(format, args);
_trace.Verbose(message);
_executionContext?.Debug(message);
}
}
}
}

View File

@@ -91,8 +91,7 @@
"strategy",
"matrix",
"job",
"runner",
"hashFiles(1,255)"
"runner"
],
"string": {}
},

View File

@@ -5,7 +5,7 @@ using GitHub.DistributedTask.Expressions2.Sdk.Functions;
namespace GitHub.DistributedTask.Expressions2
{
internal static class ExpressionConstants
public static class ExpressionConstants
{
static ExpressionConstants()
{
@@ -16,6 +16,7 @@ namespace GitHub.DistributedTask.Expressions2
AddFunction<StartsWith>("startsWith", 2, 2);
AddFunction<ToJson>("toJson", 1, 1);
AddFunction<FromJson>("fromJson", 1, 1);
AddFunction<HashFiles>("hashFiles", 1, 1);
}
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
@@ -24,6 +25,12 @@ namespace GitHub.DistributedTask.Expressions2
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
}
public static void UpdateFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
where T : Function, new()
{
WellKnownFunctions[name] = new FunctionInfo<T>(name, minParameters, maxParameters);
}
internal static readonly String False = "false";
internal static readonly String Infinity = "Infinity";
internal static readonly Int32 MaxDepth = 50;

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Minimatch;
using System.IO;
using System.Security.Cryptography;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
{
internal sealed class HashFiles : Function
{
protected sealed override Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
resultMemory = null;
// hashFiles() only works on the runner and only works with files under GITHUB_WORKSPACE
// Since GITHUB_WORKSPACE is set by runner, I am using that as the fact of this code runs on server or runner.
if (context.State is ObjectTemplating.TemplateContext templateContext &&
templateContext.ExpressionValues.TryGetValue(PipelineTemplateConstants.GitHub, out var githubContextData) &&
githubContextData is DictionaryContextData githubContext &&
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out var workspace) == true &&
workspace is StringContextData workspaceData)
{
string searchRoot = workspaceData.Value;
string pattern = Parameters[0].Evaluate(context).ConvertToString();
// Convert slashes on Windows
if (s_isWindows)
{
pattern = pattern.Replace('\\', '/');
}
// Root the pattern
if (!Path.IsPathRooted(pattern))
{
var patternRoot = s_isWindows ? searchRoot.Replace('\\', '/').TrimEnd('/') : searchRoot.TrimEnd('/');
pattern = string.Concat(patternRoot, "/", pattern);
}
// Get all files
context.Trace.Info($"Search root directory: '{searchRoot}'");
context.Trace.Info($"Search pattern: '{pattern}'");
var files = Directory.GetFiles(searchRoot, "*", SearchOption.AllDirectories)
.Select(x => s_isWindows ? x.Replace('\\', '/') : x)
.OrderBy(x => x, StringComparer.Ordinal)
.ToList();
if (files.Count == 0)
{
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Directory '{searchRoot}' is empty");
}
else
{
context.Trace.Info($"Found {files.Count} files");
}
// Match
var matcher = new Minimatcher(pattern, s_minimatchOptions);
files = matcher.Filter(files)
.Select(x => s_isWindows ? x.Replace('/', '\\') : x)
.ToList();
if (files.Count == 0)
{
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Search pattern '{pattern}' doesn't match any file under '{searchRoot}'");
}
else
{
context.Trace.Info($"{files.Count} matches to hash");
}
// Hash each file
List<byte> filesSha256 = new List<byte>();
foreach (var file in files)
{
context.Trace.Info($"Hash {file}");
using (SHA256 sha256hash = SHA256.Create())
{
using (var fileStream = File.OpenRead(file))
{
filesSha256.AddRange(sha256hash.ComputeHash(fileStream));
}
}
}
// Hash the hashes
using (SHA256 sha256hash = SHA256.Create())
{
var hashBytes = sha256hash.ComputeHash(filesSha256.ToArray());
StringBuilder hashString = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
hashString.Append(hashBytes[i].ToString("x2"));
}
var result = hashString.ToString();
context.Trace.Info($"Final hash result: '{result}'");
return result;
}
}
else
{
throw new InvalidOperationException("'hashfiles' expression function is only supported under runner context.");
}
}
private static readonly bool s_isWindows = Environment.OSVersion.Platform != PlatformID.Unix && Environment.OSVersion.Platform != PlatformID.MacOSX;
// Only support basic globbing (* ? and []) and globstar (**)
private static readonly Options s_minimatchOptions = new Options
{
Dot = true,
NoBrace = true,
NoCase = s_isWindows,
NoComment = true,
NoExt = true,
NoNegate = true,
};
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
@@ -23,27 +22,10 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
definition.RemoveAt(i);
var readerContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
var evaluatorContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
foreach (TemplateToken item in context)
{
var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value;
readerContext.Add(itemStr);
// Remove min/max parameter info
var paramIndex = itemStr.IndexOf('(');
if (paramIndex > 0)
{
evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")"));
}
else
{
evaluatorContext.Add(itemStr);
}
}
ReaderContext = readerContext.ToArray();
EvaluatorContext = evaluatorContext.ToArray();
Context = context
.Select(x => x.AssertString($"{TemplateConstants.Context} item").Value)
.Distinct()
.ToArray();
}
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
{
@@ -58,17 +40,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
internal abstract DefinitionType DefinitionType { get; }
/// <summary>
/// Used by the template reader to determine allowed expression values and functions.
/// Also used by the template reader to validate function min/max parameters.
/// </summary>
internal String[] ReaderContext { get; private set; } = new String[0];
/// <summary>
/// Used by the template evaluator to determine allowed expression values and functions.
/// The min/max parameter info is omitted.
/// </summary>
internal String[] EvaluatorContext { get; private set; } = new String[0];
internal String[] Context { get; private set; } = new String[0];
internal abstract void Validate(
TemplateSchema schema,

View File

@@ -108,7 +108,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var inherited = schema.GetDefinition(Inherits);
if (inherited.ReaderContext.Length > 0)
if (inherited.Context.Length > 0)
{
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
}

View File

@@ -62,7 +62,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var nestedDefinition = schema.GetDefinition(nestedType);
if (nestedDefinition.ReaderContext.Length > 0)
if (nestedDefinition.Context.Length > 0)
{
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
}

View File

@@ -47,16 +47,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
var evaluator = new TemplateEvaluator(context, template, removeBytes);
try
{
var availableContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
foreach (var key in context.ExpressionValues.Keys)
{
availableContext.Add(key);
}
foreach (var function in context.ExpressionFunctions)
{
availableContext.Add($"{function.Name}()");
}
var availableContext = new HashSet<String>(context.ExpressionValues.Keys.Concat(context.ExpressionFunctions.Select(x => $"{x.Name}({x.MinParameters},{x.MaxParameters})")));
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
result = evaluator.Evaluate(definitionInfo);
@@ -402,13 +393,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
m_allowedContext = Definition.EvaluatorContext;
if (Definition.EvaluatorContext.Length > 0)
if (Definition.Context.Length > 0)
{
m_allowedContext = Definition.Context;
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
}
else
{
m_allowedContext = new String[0];
Expand = false;
}
}
@@ -424,9 +416,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
if (Definition.EvaluatorContext.Length > 0)
if (Definition.Context.Length > 0)
{
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.EvaluatorContext), StringComparer.OrdinalIgnoreCase).ToArray();
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.Context)).ToArray();
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
}
else

View File

@@ -49,14 +49,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
}
public TemplateValidationException(
String message,
IEnumerable<TemplateValidationError> errors)
: this(message)
{
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
}
public TemplateValidationException(String message)
: base(message)
{

View File

@@ -780,8 +780,15 @@ namespace GitHub.DistributedTask.ObjectTemplating
// Lookup the definition
Definition = m_schema.GetDefinition(name);
// Record allowed context
AllowedContext = Definition.ReaderContext;
// Determine whether to expand
if (Definition.Context.Length > 0)
{
AllowedContext = Definition.Context;
}
else
{
AllowedContext = new String[0];
}
}
public DefinitionInfo(
@@ -793,10 +800,10 @@ namespace GitHub.DistributedTask.ObjectTemplating
// Lookup the definition
Definition = m_schema.GetDefinition(name);
// Record allowed context
if (Definition.ReaderContext.Length > 0)
// Determine whether to expand
if (Definition.Context.Length > 0)
{
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.ReaderContext), StringComparer.OrdinalIgnoreCase).ToArray();
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.Context)).ToArray();
}
else
{

View File

@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.ObjectTemplating
@@ -42,7 +41,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
{
for (int i = 0; i < 50; i++)
{
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.ToString();
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message;
Add(new TemplateValidationError(message));
if (ex.InnerException == null)
{
@@ -89,23 +88,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
}
}
/// <summary>
/// Throws <c ref="TemplateValidationException" /> if any errors.
/// <param name="prefix">The error message prefix</param>
/// </summary>
public void Check(String prefix)
{
if (String.IsNullOrEmpty(prefix))
{
this.Check();
}
else if (m_errors.Count > 0)
{
var message = $"{prefix.Trim()} {String.Join(",", m_errors.Select(e => e.Message))}";
throw new TemplateValidationException(message, m_errors);
}
}
public void Clear()
{
m_errors.Clear();

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.Services.WebApi.Internal;
@@ -37,29 +35,11 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
String[] allowedContext,
out Exception ex)
{
// Create dummy named values and functions
var namedValues = new List<INamedValueInfo>();
var functions = new List<IFunctionInfo>();
// Create dummy allowed contexts
INamedValueInfo[] namedValues = null;
if (allowedContext?.Length > 0)
{
foreach (var contextItem in allowedContext)
{
var match = s_function.Match(contextItem);
if (match.Success)
{
var functionName = match.Groups[1].Value;
var minParameters = Int32.Parse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture);
var maxParametersRaw = match.Groups[3].Value;
var maxParameters = String.Equals(maxParametersRaw, TemplateConstants.MaxConstant, StringComparison.Ordinal)
? Int32.MaxValue
: Int32.Parse(maxParametersRaw, NumberStyles.None, CultureInfo.InvariantCulture);
functions.Add(new FunctionInfo<DummyFunction>(functionName, minParameters, maxParameters));
}
else
{
namedValues.Add(new NamedValueInfo<ContextValueNode>(contextItem));
}
}
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
}
// Parse
@@ -67,7 +47,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
ExpressionNode root = null;
try
{
root = new ExpressionParser().CreateTree(expression, null, namedValues, functions) as ExpressionNode;
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
result = true;
ex = null;
@@ -80,18 +60,5 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
return result;
}
private sealed class DummyFunction : Function
{
protected override Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
resultMemory = null;
return null;
}
}
private static readonly Regex s_function = new Regex(@"^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$", RegexOptions.Compiled);
}
}

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
{
@@ -109,43 +106,6 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
}
/// <summary>
/// Traverses the token and checks whether all required expression values
/// and functions are provided.
/// </summary>
public static bool CheckHasRequiredContext(
this TemplateToken token,
IReadOnlyObject expressionValues,
IList<IFunctionInfo> expressionFunctions)
{
var expressionTokens = token.Traverse()
.OfType<BasicExpressionToken>()
.ToArray();
var parser = new ExpressionParser();
foreach (var expressionToken in expressionTokens)
{
var tree = parser.ValidateSyntax(expressionToken.Expression, null);
foreach (var node in tree.Traverse())
{
if (node is NamedValue namedValue)
{
if (expressionValues?.Keys.Any(x => string.Equals(x, namedValue.Name, StringComparison.OrdinalIgnoreCase)) != true)
{
return false;
}
}
else if (node is Function function &&
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name) &&
expressionFunctions?.Any(x => string.Equals(x.Name, function.Name, StringComparison.OrdinalIgnoreCase)) != true)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Returns all tokens (depth first)
/// </summary>

View File

@@ -24,7 +24,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public const String FetchDepth = "fetch-depth";
public const String GeneratedId = "generated-id";
public const String GitHub = "github";
public const String HashFiles = "hashFiles";
public const String Id = "id";
public const String If = "if";
public const String Image = "image";
@@ -32,7 +31,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public const String Inputs = "inputs";
public const String Job = "job";
public const String JobDefaultsRun = "job-defaults-run";
public const String JobIfResult = "job-if-result";
public const String JobOutputs = "job-outputs";
public const String Jobs = "jobs";
public const String Labels = "labels";
@@ -62,7 +60,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public const String Shell = "shell";
public const String Skipped = "skipped";
public const String StepEnv = "step-env";
public const String StepIfResult = "step-if-result";
public const String Steps = "steps";
public const String StepsScopeInputs = "steps-scope-inputs";
public const String StepsScopeOutputs = "steps-scope-outputs";

View File

@@ -16,20 +16,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
{
internal static class PipelineTemplateConverter
{
internal static Boolean ConvertToIfResult(
TemplateContext context,
TemplateToken ifResult)
{
var expression = ifResult.Traverse().FirstOrDefault(x => x is ExpressionToken);
if (expression != null)
{
throw new ArgumentException($"Unexpected type '{expression.GetType().Name}' encountered while reading 'if'.");
}
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
return evaluationResult.IsTruthy;
}
internal static Boolean? ConvertToStepContinueOnError(
TemplateContext context,
TemplateToken token,

View File

@@ -4,7 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Threading;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk.Functions;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.ObjectTemplating.Schema;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
@@ -14,9 +14,6 @@ using ITraceWriter = GitHub.DistributedTask.ObjectTemplating.ITraceWriter;
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
{
/// <summary>
/// Evaluates parts of the workflow DOM. For example, a job strategy or step inputs.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class PipelineTemplateEvaluator
{
@@ -53,14 +50,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public DictionaryContextData EvaluateStepScopeInputs(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(DictionaryContextData);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true);
@@ -80,14 +76,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public DictionaryContextData EvaluateStepScopeOutputs(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(DictionaryContextData);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true);
@@ -107,14 +102,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public Boolean EvaluateStepContinueOnError(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(Boolean?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true);
@@ -132,44 +126,16 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
return result ?? false;
}
public String EvaluateStepDisplayName(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
{
var result = default(String);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
context.Errors.Check();
result = PipelineTemplateConverter.ConvertToStepDisplayName(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public Dictionary<String, String> EvaluateStepEnvironment(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions,
StringComparer keyComparer)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true);
@@ -187,44 +153,15 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
return result ?? new Dictionary<String, String>(keyComparer);
}
public Boolean EvaluateStepIf(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions,
IEnumerable<KeyValuePair<String, Object>> expressionState)
{
var result = default(Boolean?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions, expressionState);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepIfResult, token, 0, null, omitHeader: true);
context.Errors.Check();
result = PipelineTemplateConverter.ConvertToIfResult(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? throw new InvalidOperationException("Step if cannot be null");
}
public Dictionary<String, String> EvaluateStepInputs(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true);
@@ -244,14 +181,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public Int32 EvaluateStepTimeout(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(Int32?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true);
@@ -271,14 +207,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public JobContainer EvaluateJobContainer(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(JobContainer);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true);
@@ -298,14 +233,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public Dictionary<String, String> EvaluateJobOutput(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true);
@@ -335,14 +269,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public Dictionary<String, String> EvaluateJobDefaultsRun(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
@@ -372,14 +305,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions)
DictionaryContextData contextData)
{
var result = default(List<KeyValuePair<String, JobContainer>>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData, expressionFunctions);
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
@@ -397,10 +329,62 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
return result;
}
private TemplateContext CreateContext(
public Boolean TryEvaluateStepDisplayName(
TemplateToken token,
DictionaryContextData contextData,
IList<IFunctionInfo> expressionFunctions,
IEnumerable<KeyValuePair<String, Object>> expressionState = null)
out String stepName)
{
stepName = default(String);
var context = CreateContext(contextData);
if (token != null && token.Type != TokenType.Null)
{
// We should only evaluate basic expressions if we are sure we have context on all the Named Values and functions
// Otherwise return and use a default name
if (token is BasicExpressionToken expressionToken)
{
ExpressionNode root = null;
try
{
root = new ExpressionParser().ValidateSyntax(expressionToken.Expression, null) as ExpressionNode;
}
catch (Exception exception)
{
context.Errors.Add(exception);
context.Errors.Check();
}
foreach (var node in root.Traverse())
{
if (node is NamedValue namedValue && !contextData.ContainsKey(namedValue.Name))
{
return false;
}
else if (node is Function function &&
!context.ExpressionFunctions.Any(item => String.Equals(item.Name, function.Name)) &&
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name))
{
return false;
}
}
}
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
context.Errors.Check();
stepName = PipelineTemplateConverter.ConvertToStepDisplayName(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return true;
}
private TemplateContext CreateContext(DictionaryContextData contextData)
{
var result = new TemplateContext
{
@@ -423,7 +407,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
}
}
// Add named values
// Add named context
if (contextData != null)
{
foreach (var pair in contextData)
@@ -432,46 +416,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
}
}
// Add functions
var functionNames = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
if (expressionFunctions?.Count > 0)
{
foreach (var function in expressionFunctions)
{
result.ExpressionFunctions.Add(function);
functionNames.Add(function.Name);
}
}
// Add missing expression values and expression functions.
// This solves the following problems:
// - Compat for new agent against old server (new contexts not sent down in job message)
// - Evaluating early when all referenced contexts are available, even though all allowed
// contexts may not yet be available. For example, evaluating step display name can often
// be performed early.
foreach (var name in s_expressionValueNames)
// Compat for new agent against old server
foreach (var name in s_contextNames)
{
if (!result.ExpressionValues.ContainsKey(name))
{
result.ExpressionValues[name] = null;
}
}
foreach (var name in s_expressionFunctionNames)
{
if (!functionNames.Contains(name))
{
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
}
}
// Add state
if (expressionState != null)
{
foreach (var pair in expressionState)
{
result.State[pair.Key] = pair.Value;
}
}
return result;
}
@@ -479,10 +431,9 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
private readonly ITraceWriter m_trace;
private readonly TemplateSchema m_schema;
private readonly IList<String> m_fileTable;
private readonly String[] s_expressionValueNames = new[]
private readonly String[] s_contextNames = new[]
{
PipelineTemplateConstants.GitHub,
PipelineTemplateConstants.Needs,
PipelineTemplateConstants.Strategy,
PipelineTemplateConstants.Matrix,
PipelineTemplateConstants.Needs,
@@ -493,13 +444,5 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
PipelineTemplateConstants.Runner,
PipelineTemplateConstants.Env,
};
private readonly String[] s_expressionFunctionNames = new[]
{
PipelineTemplateConstants.Always,
PipelineTemplateConstants.Cancelled,
PipelineTemplateConstants.Failure,
PipelineTemplateConstants.HashFiles,
PipelineTemplateConstants.Success,
};
}
}

View File

@@ -2,35 +2,25 @@
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating.Schema;
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class PipelineTemplateSchemaFactory
public sealed class PipelineTemplateSchemaFactory
{
public static TemplateSchema GetSchema()
public TemplateSchema CreateSchema()
{
if (s_schema == null)
var assembly = Assembly.GetExecutingAssembly();
var json = default(String);
using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json"))
using (var streamReader = new StreamReader(stream))
{
var assembly = Assembly.GetExecutingAssembly();
var json = default(String);
using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json"))
using (var streamReader = new StreamReader(stream))
{
json = streamReader.ReadToEnd();
}
var objectReader = new JsonObjectReader(null, json);
var schema = TemplateSchema.Load(objectReader);
Interlocked.CompareExchange(ref s_schema, schema, null);
json = streamReader.ReadToEnd();
}
return s_schema;
var objectReader = new JsonObjectReader(null, json);
return TemplateSchema.Load(objectReader);
}
private static TemplateSchema s_schema;
}
}

View File

@@ -38,8 +38,8 @@
"steps-scope-input-value": {
"context": [
"github",
"needs",
"strategy",
"needs",
"matrix",
"secrets",
"steps",
@@ -66,9 +66,9 @@
"steps-scope-output-value": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
@@ -91,9 +91,9 @@
"description": "Default input values for a steps template",
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"one-of": [
"string",
@@ -114,9 +114,9 @@
"description": "Output values for a steps template",
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
@@ -204,25 +204,6 @@
"string": {}
},
"job-if-result": {
"context": [
"github",
"needs",
"always(0,0)",
"failure(0,MAX)",
"cancelled(0,0)",
"success(0,MAX)"
],
"one-of": [
"null",
"boolean",
"number",
"string",
"sequence",
"mapping"
]
},
"strategy": {
"context": [
"github",
@@ -291,9 +272,9 @@
"runs-on": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"one-of": [
"non-empty-string",
@@ -316,10 +297,10 @@
"job-env": {
"context": [
"github",
"needs",
"secrets",
"strategy",
"matrix",
"secrets"
"needs"
],
"mapping": {
"loose-key-type": "non-empty-string",
@@ -463,9 +444,9 @@
"step-if": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"steps",
"job",
"runner",
@@ -473,8 +454,7 @@
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)",
"hashFiles(1,255)"
"success(0,0)"
],
"string": {}
},
@@ -482,9 +462,9 @@
"step-if-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"steps",
"inputs",
"job",
@@ -493,63 +473,11 @@
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)",
"hashFiles(1,255)"
"success(0,0)"
],
"string": {}
},
"step-if-result": {
"context": [
"github",
"strategy",
"matrix",
"steps",
"job",
"runner",
"env",
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)",
"hashFiles(1,255)"
],
"one-of": [
"null",
"boolean",
"number",
"string",
"sequence",
"mapping"
]
},
"step-if-result-in-template": {
"context": [
"github",
"strategy",
"matrix",
"steps",
"inputs",
"job",
"runner",
"env",
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)",
"hashFiles(1,255)"
],
"one-of": [
"null",
"boolean",
"number",
"string",
"sequence",
"mapping"
]
},
"steps-template-reference": {
"mapping": {
"properties": {
@@ -573,9 +501,9 @@
"steps-template-reference-inputs": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
@@ -591,9 +519,9 @@
"steps-template-reference-inputs-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
@@ -610,15 +538,14 @@
"step-env": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"mapping": {
"loose-key-type": "non-empty-string",
@@ -629,16 +556,15 @@
"step-env-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"mapping": {
"loose-key-type": "non-empty-string",
@@ -649,35 +575,14 @@
"step-with": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
],
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "string"
}
},
"step-with-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"mapping": {
"loose-key-type": "non-empty-string",
@@ -688,9 +593,9 @@
"container": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"one-of": [
"string",
@@ -713,9 +618,9 @@
"services": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"mapping": {
"loose-key-type": "non-empty-string",
@@ -726,9 +631,9 @@
"services-container": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"one-of": [
"non-empty-string",
@@ -743,6 +648,25 @@
}
},
"step-with-in-template": {
"context": [
"github",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env"
],
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "string"
}
},
"non-empty-string": {
"string": {
"require-non-empty": true
@@ -758,9 +682,9 @@
"boolean-strategy-context": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"boolean": {}
},
@@ -768,9 +692,9 @@
"number-strategy-context": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"number": {}
},
@@ -778,9 +702,9 @@
"string-strategy-context": {
"context": [
"github",
"needs",
"strategy",
"matrix"
"matrix",
"needs"
],
"string": {}
},
@@ -788,15 +712,14 @@
"boolean-steps-context": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"boolean": {}
},
@@ -804,16 +727,15 @@
"boolean-steps-context-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"boolean": {}
},
@@ -821,15 +743,14 @@
"number-steps-context": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"number": {}
},
@@ -837,16 +758,15 @@
"number-steps-context-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"number": {}
},
@@ -854,9 +774,9 @@
"string-runner-context": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
@@ -869,15 +789,14 @@
"string-steps-context": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"string": {}
},
@@ -885,16 +804,15 @@
"string-steps-context-in-template": {
"context": [
"github",
"needs",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"inputs",
"job",
"runner",
"env",
"hashFiles(1,255)"
"env"
],
"string": {}
}

View File

@@ -1,6 +1,4 @@
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Worker;
@@ -111,57 +109,6 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void PrepareActions_SkipDownloadActionFromGraphWhenCached_OnPremises()
{
try
{
// Arrange
Setup();
var actionId = Guid.NewGuid();
var actions = new List<Pipelines.ActionStep>
{
new Pipelines.ActionStep()
{
Name = "action",
Id = actionId,
Reference = new Pipelines.RepositoryPathReference()
{
Name = "actions/no-such-action",
Ref = "master",
RepositoryType = "GitHub"
}
}
};
_configurationStore.Object.GetSettings().IsHostedServer = false;
var actionDirectory = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/no-such-action", "master");
Directory.CreateDirectory(actionDirectory);
var watermarkFile = $"{actionDirectory}.completed";
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
var actionFile = Path.Combine(actionDirectory, "action.yml");
File.WriteAllText(actionFile, @"
name: ""no-such-action""
runs:
using: node12
main: no-such-action.js
");
var testFile = Path.Combine(actionDirectory, "test-file");
File.WriteAllText(testFile, "asdf");
// Act
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
// Assert
Assert.True(File.Exists(testFile));
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
@@ -1653,8 +1600,6 @@ runs:
_ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));

View File

@@ -1,9 +1,7 @@
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Expressions;
using Moq;
using System;
using System.Collections.Generic;
@@ -535,26 +533,26 @@ namespace GitHub.Runner.Common.Tests.Worker
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
_ec.Object.ExpressionValues["github"] = new DictionaryContextData
{
{ "ref", new StringContextData("refs/heads/master") },
};
_ec.Object.ExpressionValues["strategy"] = new DictionaryContextData();
_ec.Object.ExpressionValues["matrix"] = new DictionaryContextData();
_ec.Object.ExpressionValues["steps"] = new DictionaryContextData();
_ec.Object.ExpressionValues["job"] = new DictionaryContextData();
_ec.Object.ExpressionValues["runner"] = new DictionaryContextData();
_ec.Object.ExpressionValues["env"] = new DictionaryContextData();
_ec.Object.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>("hashFiles", 1, 255));
var githubContext = new DictionaryContextData();
githubContext.Add("ref", new StringContextData("refs/heads/master"));
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
evaluateContext["github"] = githubContext;
evaluateContext["strategy"] = new DictionaryContextData();
evaluateContext["matrix"] = new DictionaryContextData();
evaluateContext["steps"] = new DictionaryContextData();
evaluateContext["job"] = new DictionaryContextData();
evaluateContext["runner"] = new DictionaryContextData();
evaluateContext["env"] = new DictionaryContextData();
//Act
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"));
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"), evaluateContext);
//Assert
Assert.Equal("defaultValue", result);
//Act
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"));
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"), evaluateContext);
//Assert
Assert.Equal("refs/heads/master", result);
@@ -577,8 +575,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_ec.Setup(x => x.WriteDebug).Returns(true);
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
}

View File

@@ -1,5 +1,4 @@
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
@@ -323,7 +322,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.ExpressionValues).Returns(_context);
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));

View File

@@ -1,20 +1,20 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Expressions;
using Moq;
using Xunit;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Pipelines.ContextData;
namespace GitHub.Runner.Common.Tests.Worker.Expressions
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class ConditionFunctionsL0
public sealed class ExpressionManagerL0
{
private TemplateContext _templateContext;
private Mock<IExecutionContext> _ec;
private ExpressionManager _expressionManager;
private DictionaryContextData _expressions;
private JobContext _jobContext;
[Fact]
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = Evaluate("always()");
bool actual = _expressionManager.Evaluate(_ec.Object, "always()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = Evaluate("cancelled()");
bool actual = _expressionManager.Evaluate(_ec.Object, "cancelled()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = Evaluate("failure()");
bool actual = _expressionManager.Evaluate(_ec.Object, "failure()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
@@ -126,7 +126,37 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = Evaluate("success()");
bool actual = _expressionManager.Evaluate(_ec.Object, "success()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ContextNamedValue()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var variableSets = new[]
{
new { Condition = "github.ref == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
new { Condition = "github['ref'] == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
new { Condition = "github.nosuch || '' == ''", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
new { Condition = "github['ref'] == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
new { Condition = "github.ref == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
};
foreach (var variableSet in variableSets)
{
InitializeExecutionContext(hc);
_ec.Object.ExpressionValues["github"] = new GitHubContext() { { variableSet.VariableName, new StringContextData(variableSet.VariableValue) } };
// Act.
bool actual = _expressionManager.Evaluate(_ec.Object, variableSet.Condition).Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
@@ -136,34 +166,21 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
return new TestHostContext(this, testName);
var hc = new TestHostContext(this, testName);
_expressionManager = new ExpressionManager();
_expressionManager.Initialize(hc);
return hc;
}
private void InitializeExecutionContext(TestHostContext hc)
{
_expressions = new DictionaryContextData();
_jobContext = new JobContext();
var executionContext = new Mock<IExecutionContext>();
executionContext.SetupAllProperties();
executionContext.Setup(x => x.JobContext).Returns(_jobContext);
_templateContext = new TemplateContext();
_templateContext.State[nameof(IExecutionContext)] = executionContext.Object;
}
private bool Evaluate(string expression)
{
var parser = new ExpressionParser();
var functions = new IFunctionInfo[]
{
new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0),
new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0),
new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0),
new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0),
};
var tree = parser.CreateTree(expression, null, null, functions);
var result = tree.Evaluate(null, null, _templateContext, null);
return result.IsTruthy;
_ec = new Mock<IExecutionContext>();
_ec.SetupAllProperties();
_ec.Setup(x => x.ExpressionValues).Returns(_expressions);
_ec.Setup(x => x.JobContext).Returns(_jobContext);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IJobServerQueue> _jobServerQueue;
private Mock<IConfigurationStore> _config;
private Mock<IPagingLogger> _logger;
private Mock<IExpressionManager> _express;
private Mock<IContainerOperationProvider> _containerProvider;
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
@@ -34,6 +35,7 @@ namespace GitHub.Runner.Common.Tests.Worker
_jobServerQueue = new Mock<IJobServerQueue>();
_config = new Mock<IConfigurationStore>();
_logger = new Mock<IPagingLogger>();
_express = new Mock<IExpressionManager>();
_containerProvider = new Mock<IContainerOperationProvider>();
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
_directoryManager = new Mock<IPipelineDirectoryManager>();
@@ -106,6 +108,7 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.SetSingleton(_actionManager.Object);
hc.SetSingleton(_config.Object);
hc.SetSingleton(_jobServerQueue.Object);
hc.SetSingleton(_express.Object);
hc.SetSingleton(_containerProvider.Object);
hc.SetSingleton(_directoryManager.Object);
hc.SetSingleton(_diagnosticLogManager.Object);

View File

@@ -53,6 +53,9 @@ namespace GitHub.Runner.Common.Tests.Worker
}
_tokenSource = new CancellationTokenSource();
var expressionManager = new ExpressionManager();
expressionManager.Initialize(hc);
hc.SetSingleton<IExpressionManager>(expressionManager);
_jobRunner = new JobRunner();
_jobRunner.Initialize(hc);

View File

@@ -1,17 +1,17 @@
using System;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Moq;
using Xunit;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Worker;
namespace GitHub.Runner.Common.Tests.Worker
{
@@ -27,6 +27,9 @@ namespace GitHub.Runner.Common.Tests.Worker
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
var hc = new TestHostContext(this, testName);
var expressionManager = new ExpressionManager();
expressionManager.Initialize(hc);
hc.SetSingleton<IExpressionManager>(expressionManager);
Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
_variables = new Variables(
hostContext: hc,
@@ -46,7 +49,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_contexts["runner"] = new DictionaryContextData();
_contexts["job"] = _jobContext;
_ec.Setup(x => x.ExpressionValues).Returns(_contexts);
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.JobContext).Returns(_jobContext);
_stepContext = new StepsContext();
@@ -381,11 +383,16 @@ namespace GitHub.Runner.Common.Tests.Worker
{
using (TestHostContext hc = CreateTestContext())
{
var expressionManager = new Mock<IExpressionManager>();
expressionManager.Object.Initialize(hc);
hc.SetSingleton<IExpressionManager>(expressionManager.Object);
expressionManager.Setup(x => x.Evaluate(It.IsAny<IExecutionContext>(), It.IsAny<string>(), It.IsAny<bool>())).Throws(new Exception());
// Arrange.
var variableSets = new[]
{
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
};
foreach (var variableSet in variableSets)
{
@@ -603,7 +610,6 @@ namespace GitHub.Runner.Common.Tests.Worker
stepContext.Setup(x => x.Variables).Returns(_variables);
stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
stepContext.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);

View File

@@ -1 +1 @@
2.168.0
2.165.2