Compare commits

..

7 Commits

Author SHA1 Message Date
Brian Cristante
d8a53aa04a Remove dead code 2021-10-14 17:40:49 -04:00
Brian Cristante
9fe65b872a Fix detecting if the step is run: make 2021-10-14 17:16:50 -04:00
Brian Cristante
ec420bab24 Try adding steps in JobExtension 2021-10-14 16:26:00 -04:00
Brian Cristante
07cda747b2 Create a new step host for each sub-step 2021-10-14 13:59:34 -04:00
Brian Cristante
9ca132380c Detect Makefiles and parse out the targets 2021-10-14 11:56:26 -04:00
Brian Cristante
a5a0f8df47 Revert "Add GNUMakeManager"
This reverts commit ccf53338be.
2021-10-14 09:09:58 -04:00
Brian Cristante
ccf53338be Add GNUMakeManager 2021-10-12 14:11:14 -04:00
53 changed files with 418 additions and 532 deletions

View File

@@ -5,7 +5,7 @@
# GitHub Actions Runner
[![Actions Status](https://github.com/actions/runner/workflows/Runner%20CI/badge.svg)](https://github.com/actions/runner/actions)
[![Runner E2E Test](https://github.com/actions-canary/actions-runner-e2e/actions/workflows/runner_e2etest.yml/badge.svg)](https://github.com/actions-canary/actions-runner-e2e/actions/workflows/runner_e2etest.yml)
[![Runner E2E Test](https://github.com/actions/runner/workflows/Runner%20E2E%20Test/badge.svg)](https://github.com/actions/runner/actions)
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.

View File

@@ -1,71 +0,0 @@
# ADR 1438: Support Conditionals In Composite Actions
**Date**: 2021-10-13
**Status**: Accepted
## Context
We recently shipped composite actions, which allows you to reuse individual steps inside an action.
However, one of the [most requested features](https://github.com/actions/runner/issues/834) has been a way to support the `if` keyword.
### Goals
- We want to keep consistent with current behavior
- We want to support conditionals via the `if` keyword
- Our built in functions like `success` should be implementable without calling them, for example you can do `job.status == success` rather then `success()` currently.
### How does composite currently work?
Currently, we have limited conditional support in composite actions for `pre` and `post` steps.
These are based on the `job status`, and support keywords like `always()`, `failed()`, `success()` and `cancelled()`.
However, generic or main steps do **not** support conditionals.
By default, in a regular workflow, a step runs on the `success()` condition. Which looks at the **job** **status**, sees if it is successful and runs.
By default, in a composite action, main steps run until a single step fails in that composite action, then the composite action is halted early. It does **not** care about the job status.
Pre, and post steps in composite actions use the job status to determine if they should run.
### How do we go forward?
Well, if we think about what composite actions are currently doing when invoking main steps, they are checking if the current composite action is successful.
Lets formalize that concept into a "real" idea.
- We will add an `action_status` field to the github context to mimic the [job's context status](https://docs.github.com/en/actions/learn-github-actions/contexts#job-context).
- We have an existing concept that does this `action_path` which is only set for composite actions on the github context.
- In a composite action during a main step, the `success()` function will check if `action_status == success`, rather then `job_status == success`. Failure will work the same way.
- Pre and post steps in composite actions will not change, they will continue to check the job status.
### Nested Scenario
For nested composite actions, we will follow the existing behavior, you only care about your current composite action, not any parents.
For example, lets imagine a scenario with a simple nested composite action
```
- Job
- Regular Step
- Composite Action
- runs: exit 1
- if: always()
uses: A child composite action
- if: success()
runs: echo "this should print"
- runs: echo "this should also print"
- if: success()
runs: echo "this will not print as the current composite action has failed already"
```
The child composite actions steps should run in this example, the child composite action has not yet failed, so it should run all steps until a step fails. This is consistent with how a composite action currently works in production if the main job fails but a composite action is invoked with `if:always()` or `if: failure()`
### Other options explored
We could add the `current_step_status` to the job context rather then `__status` to the steps context, however this comes with two major downsides:
- We need to support the field for every type of step, because its non trivial to remove a field from the job context once it has been added (its readonly)
- For all actions besides composite it would only every be `success`
- Its weird to have a `current_step` value on the job context
- We also explored a `__status` on the steps context.
- The `__` is required to prevent us from colliding with a step with id: status
- This felt wrong because the naming was not smooth, and did not fit into current conventions.
### Consequences
- github context has a new field for the status of the current composite action.
- We support conditional's in composite actions
- We keep the existing behavior for all users, but allow them to expand that functionality.

View File

@@ -1,20 +1,11 @@
## Features
- Expose GITHUB_REF_* as environment variable (#1314)
- Add arch to runner context (#1372)
- Support Conditional Steps in Composite Actions (#1438)
- Log current runner version in terminal (#1441)
## Bugs
- Makes the user keychains available to the service (#847)
- Use Actions Service health and api.github.com endpoints after connection failure on Actions Server and Hosted (#1385)
- Fix an issue where nested local composite actions did not correctly register post steps (#1433)
- Fixed an issue where ephemeral runners did not restart after upgrading (#1396)
## Misc
- Cleanup Older versions on MacOS now that we recreate node versions as needed (#1410)
## 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

@@ -161,13 +161,59 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
fi
else
runproc=$(ps x -o pgid,command | grep "run.sh" | grep -v grep | awk '{print $1}')
if [[ $? -eq 0 && -n "$runproc" ]]
then
date "+[%F %T-%4N] Running as ephemeral using run.sh, no need to recreate node folder" >> "$logfile" 2>&1
else
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
fi
if [ $attemptedtargetedfix -eq 0 ]
then
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$logfile" 2>&1
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$telemetryfile" 2>&1
if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.280.3/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node"
fi
if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.280.2/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node"
fi
if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.280.1/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node"
fi
# GHES 3.2
if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.279.0/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node"
fi
# GHES 3.1.2 or later
if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.278.0/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node"
fi
# GHES 3.1.0
if [[ ! -e "$rootfolder/externals.2.276.1/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.276.1/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.276.1/node12/bin/node"
fi
# GHES 3.0
if [[ ! -e "$rootfolder/externals.2.273.5/node12/bin/node" ]]
then
mkdir -p "$rootfolder/externals.2.273.5/node12/bin"
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.273.5/node12/bin/node"
fi
fi
fi

View File

@@ -38,8 +38,7 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(VssConnection jobConnection)
{
_connection = jobConnection;
int totalAttempts = 5;
int attemptCount = totalAttempts;
int attemptCount = 5;
var configurationStore = HostContext.GetService<IConfigurationStore>();
var runnerSettings = configurationStore.GetSettings();
@@ -57,21 +56,18 @@ namespace GitHub.Runner.Common
if (runnerSettings.IsHostedServer)
{
await CheckNetworkEndpointsAsync(attemptCount);
await CheckNetworkEndpointsAsync();
}
}
int attempt = totalAttempts - attemptCount;
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(attempt, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(3.2), TimeSpan.FromMilliseconds(100));
await Task.Delay(backoff);
await Task.Delay(100);
}
_taskClient = _connection.GetClient<TaskHttpClient>();
_hasConnection = true;
}
private async Task CheckNetworkEndpointsAsync(int attemptsLeft)
private async Task CheckNetworkEndpointsAsync()
{
try
{
@@ -83,8 +79,8 @@ namespace GitHub.Runner.Common
actionsClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
// Call the _apis/health endpoint, and include how many attempts are left as a URL query for easy tracking
var response = await actionsClient.GetAsync(new Uri(baseUri, $"_apis/health?_internalRunnerAttemptsLeft={attemptsLeft}"));
// Call the _apis/health endpoint
var response = await actionsClient.GetAsync(new Uri(baseUri, "_apis/health"));
Trace.Info($"Actions health status code: {response.StatusCode}");
}
}
@@ -104,8 +100,8 @@ namespace GitHub.Runner.Common
{
gitHubClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
// Call the api.github.com endpoint, and include how many attempts are left as a URL query for easy tracking
var response = await gitHubClient.GetAsync($"https://api.github.com?_internalRunnerAttemptsLeft={attemptsLeft}");
// Call the api.github.com endpoint
var response = await gitHubClient.GetAsync("https://api.github.com");
Trace.Info($"api.github.com status code: {response.StatusCode}");
}
}

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -243,7 +243,6 @@ namespace GitHub.Runner.Listener
validator: Validators.ServerUrlValidator);
}
#if OS_WINDOWS
public string GetWindowsLogonAccount(string defaultValue, string descriptionMsg)
{
return GetArgOrPrompt(
@@ -252,7 +251,6 @@ namespace GitHub.Runner.Listener
defaultValue: defaultValue,
validator: Validators.NTAccountValidator);
}
#endif
public string GetWindowsLogonPassword(string accountName)
{

View File

@@ -67,7 +67,6 @@ namespace GitHub.Runner.Listener.Configuration
return !string.IsNullOrEmpty(value);
}
#if OS_WINDOWS
public static bool NTAccountValidator(string arg)
{
if (string.IsNullOrEmpty(arg) || String.IsNullOrEmpty(arg.TrimStart('.', '\\')))
@@ -88,6 +87,5 @@ namespace GitHub.Runner.Listener.Configuration
return true;
}
#endif
}
}

View File

@@ -129,7 +129,7 @@ namespace GitHub.Runner.Listener
private static void LoadAndSetEnv()
{
var binDir = Path.GetDirectoryName(AppContext.BaseDirectory);
var binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var rootDir = new DirectoryInfo(binDir).Parent.FullName;
string envFile = Path.Combine(rootDir, ".env");
if (File.Exists(envFile))

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -312,8 +312,6 @@ namespace GitHub.Runner.Listener
}
HostContext.WritePerfCounter("SessionCreated");
_term.WriteLine($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
_term.WriteLine($"{DateTime.UtcNow:u}: Listening for Jobs");
IJobDispatcher jobDispatcher = null;

View File

@@ -75,9 +75,11 @@ namespace GitHub.Runner.Listener
Trace.Info($"All running job has exited.");
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
#if !OS_OSX
// delete runner backup
DeletePreviousVersionRunnerBackup(token);
Trace.Info($"Delete old version runner backup.");
#endif
// generate update script from template
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");

View File

@@ -95,7 +95,7 @@ namespace GitHub.Runner.PluginHost
string assemblyFilename = assembly.Name + ".dll";
if (string.IsNullOrEmpty(executingAssemblyLocation))
{
executingAssemblyLocation = Path.GetDirectoryName(AppContext.BaseDirectory);
executingAssemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
return context.LoadFromAssemblyPath(Path.Combine(executingAssemblyLocation, assemblyFilename));
}

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -444,7 +444,7 @@ namespace GitHub.Runner.Plugins.Artifact
{
// We should never
context.Error($"Error '{ex.Message}' when downloading file '{fileToDownload}'. (Downloader {downloaderId})");
throw;
throw ex;
}
}
@@ -528,7 +528,7 @@ namespace GitHub.Runner.Plugins.Artifact
catch (Exception ex)
{
context.Output($"File error '{ex.Message}' when uploading file '{fileToUpload}'.");
throw;
throw ex;
}
}

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -267,19 +267,6 @@ namespace GitHub.Runner.Worker
_cachedEmbeddedPostSteps[parentStepId].Push(clonedAction);
}
}
else if (depth > 0)
{
// if we're in a composite action and haven't loaded the local action yet
// we assume it has a post step
if (!_cachedEmbeddedPostSteps.ContainsKey(parentStepId))
{
// If we haven't done so already, add the parent to the post steps
_cachedEmbeddedPostSteps[parentStepId] = new Stack<Pipelines.ActionStep>();
}
// Clone action so we can modify the condition without affecting the original
var clonedAction = action.Clone() as Pipelines.ActionStep;
_cachedEmbeddedPostSteps[parentStepId].Push(clonedAction);
}
}
}
@@ -1047,6 +1034,7 @@ namespace GitHub.Runner.Worker
}
}
// TODO: remove once we remove the DistributedTask.EnableCompositeActions FF
foreach (var step in compositeAction.Steps)
{
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)

View File

@@ -262,6 +262,16 @@ namespace GitHub.Runner.Worker
environment[$"STATE_{state.Key}"] = state.Value ?? string.Empty;
}
// HACK
if (Action.DisplayName != null && Action.DisplayName.StartsWith("make dependency"))
{
var target = Action.DisplayName.Split()[2];
inputs = new Dictionary<string, string>
{
["script"] = $"make {target}"
};
}
// Create the handler.
IHandler handler = handlerFactory.Create(
ExecutionContext,

View File

@@ -40,7 +40,6 @@ namespace GitHub.Runner.Worker
string ScopeName { get; }
string SiblingScopeName { get; }
string ContextName { get; }
ActionRunStage Stage { get; }
Task ForceCompleted { get; }
TaskResult? Result { get; set; }
TaskResult? Outcome { get; set; }
@@ -63,7 +62,7 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps
Stack<IStep> PostJobSteps { get; }
Dictionary<Guid, string> EmbeddedStepsWithPostRegistered { get; }
HashSet<Guid> EmbeddedStepsWithPostRegistered{ get; }
// Keep track of embedded steps states
Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; }
@@ -77,8 +76,8 @@ namespace GitHub.Runner.Worker
// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken();
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
// logging
long Write(string tag, string message);
@@ -145,7 +144,6 @@ namespace GitHub.Runner.Worker
public string ScopeName { get; private set; }
public string SiblingScopeName { get; private set; }
public string ContextName { get; private set; }
public ActionRunStage Stage { get; private set; }
public Task ForceCompleted => _forceCompleted.Task;
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
public Dictionary<string, string> IntraActionState { get; private set; }
@@ -170,7 +168,7 @@ namespace GitHub.Runner.Worker
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
// Only job level ExecutionContext has EmbeddedStepsWithPostRegistered
public Dictionary<Guid, string> EmbeddedStepsWithPostRegistered { get; private set; }
public HashSet<Guid> EmbeddedStepsWithPostRegistered { get; private set; }
public Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; private set; }
@@ -267,19 +265,12 @@ namespace GitHub.Runner.Worker
string siblingScopeName = null;
if (this.IsEmbedded)
{
if (step is IActionRunner actionRunner)
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
{
if (Root.EmbeddedStepsWithPostRegistered.ContainsKey(actionRunner.Action.Id))
{
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to child post step stack.");
}
else
{
Root.EmbeddedStepsWithPostRegistered[actionRunner.Action.Id] = actionRunner.Condition;
}
return;
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to child post step stack.");
}
}
return;
}
else if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
{
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
@@ -294,7 +285,7 @@ namespace GitHub.Runner.Worker
Root.PostJobSteps.Push(step);
}
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
{
Trace.Entering();
@@ -303,7 +294,6 @@ namespace GitHub.Runner.Worker
child.Global = Global;
child.ScopeName = scopeName;
child.ContextName = contextName;
child.Stage = stage;
child.EmbeddedId = embeddedId;
child.SiblingScopeName = siblingScopeName;
child.JobTelemetry = JobTelemetry;
@@ -354,9 +344,9 @@ namespace GitHub.Runner.Worker
/// An embedded execution context shares the same record ID, record name, logger,
/// and a linked cancellation token.
/// </summary>
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
{
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
}
public void Start(string currentOperation = null)
@@ -553,8 +543,8 @@ namespace GitHub.Runner.Worker
}
_record.WarningCount++;
}
else if (issue.Type == IssueType.Notice)
}
else if (issue.Type == IssueType.Notice)
{
// tracking line number for each issue in log file
@@ -726,10 +716,10 @@ namespace GitHub.Runner.Worker
StepsWithPostRegistered = new HashSet<Guid>();
// EmbeddedStepsWithPostRegistered for job ExecutionContext
EmbeddedStepsWithPostRegistered = new Dictionary<Guid, string>();
EmbeddedStepsWithPostRegistered = new HashSet<Guid>();
// EmbeddedIntraActionState for job ExecutionContext
EmbeddedIntraActionState = new Dictionary<Guid, Dictionary<string, string>>();
EmbeddedIntraActionState = new Dictionary<Guid, Dictionary<string,string>>();
// Job timeline record.
InitializeTimelineRecord(
@@ -947,7 +937,7 @@ namespace GitHub.Runner.Worker
}
var newGuid = Guid.NewGuid();
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
}
}
@@ -980,7 +970,7 @@ namespace GitHub.Runner.Worker
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void InfrastructureError(this IExecutionContext context, string message)
{
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true});
}
// Do not add a format string overload. See comment on ExecutionContext.Write().

View File

@@ -24,19 +24,8 @@ namespace GitHub.Runner.Worker.Expressions
ArgUtil.NotNull(templateContext, nameof(templateContext));
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
if (isCompositeMainStep)
{
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
return actionStatus == ActionResult.Failure;
}
else
{
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Failure;
}
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Failure;
}
}
}

View File

@@ -59,7 +59,7 @@ namespace GitHub.Runner.Worker.Expressions
context.Trace.Info($"Search root directory: '{githubWorkspace}'");
context.Trace.Info($"Search pattern: '{string.Join(", ", patterns)}'");
string binDir = Path.GetDirectoryName(AppContext.BaseDirectory);
string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string runnerRoot = new DirectoryInfo(binDir).Parent.FullName;
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");

View File

@@ -24,19 +24,8 @@ namespace GitHub.Runner.Worker.Expressions
ArgUtil.NotNull(templateContext, nameof(templateContext));
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
ArgUtil.NotNull(executionContext, nameof(executionContext));
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
if (isCompositeMainStep)
{
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
return actionStatus == ActionResult.Success;
}
else
{
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Success;
}
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
return jobStatus == ActionResult.Success;
}
}
}

View File

@@ -23,9 +23,6 @@ namespace GitHub.Runner.Worker
"job",
"path",
"ref",
"ref_name",
"ref_protected",
"ref_type",
"repository",
"repository_owner",
"retention_days",
@@ -42,16 +39,9 @@ namespace GitHub.Runner.Worker
{
foreach (var data in this)
{
if (_contextEnvAllowlist.Contains(data.Key))
if (_contextEnvAllowlist.Contains(data.Key) && data.Value is StringContextData value)
{
if (data.Value is StringContextData value)
{
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
}
else if (data.Value is BooleanContextData booleanValue)
{
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", booleanValue.ToString());
}
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
}
}
}

View File

@@ -50,9 +50,8 @@ namespace GitHub.Runner.Worker.Handlers
// Only register post steps for steps that actually ran
foreach (var step in Data.PostSteps.ToList())
{
if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.ContainsKey(step.Id))
if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.Contains(step.Id))
{
step.Condition = ExecutionContext.Root.EmbeddedStepsWithPostRegistered[step.Id];
steps.Add(step);
}
else
@@ -125,7 +124,7 @@ namespace GitHub.Runner.Worker.Handlers
{
ArgUtil.NotNull(step, step.DisplayName);
var stepId = $"__{Guid.NewGuid()}";
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid(), stage);
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid());
embeddedSteps.Add(step);
}
}
@@ -144,7 +143,7 @@ namespace GitHub.Runner.Worker.Handlers
step.Stage = stage;
step.Condition = stepData.Condition;
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, stage, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName))
{
@@ -242,10 +241,6 @@ namespace GitHub.Runner.Worker.Handlers
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0));
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
// Set action_status to the success of the current composite action
var actionResult = ExecutionContext.Result?.ToActionResult() ?? ActionResult.Success;
step.ExecutionContext.SetGitHubContext("action_status", actionResult.ToString());
// Initialize env context
Trace.Info("Initialize Env context for embedded step");
#if OS_WINDOWS
@@ -300,100 +295,108 @@ namespace GitHub.Runner.Worker.Handlers
CancellationTokenRegistration? jobCancelRegister = null;
try
{
// Register job cancellation call back only if job cancellation token not been fire before each step run
if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
{
// Test the condition again. The job was canceled after the condition was originally evaluated.
jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() =>
{
// Mark job as cancelled
ExecutionContext.Root.Result = TaskResult.Canceled;
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.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;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
}
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());
}
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);
}
}
if (!conditionReTestResult)
{
// Cancel the step
Trace.Info("Cancel current running step.");
step.ExecutionContext.CancelToken();
}
});
}
else
{
if (ExecutionContext.Root.Result != TaskResult.Canceled)
{
// Mark job as cancelled
ExecutionContext.Root.Result = TaskResult.Canceled;
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
}
}
// 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);
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
}
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());
}
catch (Exception ex)
{
Trace.Info("Caught exception from expression.");
Trace.Error(ex);
conditionEvaluateError = ex;
}
}
if (!conditionResult && conditionEvaluateError == null)
{
// Condition is false
Trace.Info("Skipping step due to condition evaluation.");
step.ExecutionContext.Result = TaskResult.Skipped;
continue;
}
else if (conditionEvaluateError != null)
{
// Condition error
step.ExecutionContext.Error(conditionEvaluateError);
step.ExecutionContext.Result = TaskResult.Failed;
ExecutionContext.Result = TaskResult.Failed;
break;
}
else
// For main steps just run the action
if (stage == ActionRunStage.Main)
{
await RunStepAsync(step);
}
// We need to evaluate conditions for pre/post steps
else
{
// Register job cancellation call back only if job cancellation token not been fire before each step run
if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
{
// Test the condition again. The job was canceled after the condition was originally evaluated.
jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() =>
{
// Mark job as cancelled
ExecutionContext.Root.Result = TaskResult.Canceled;
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.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;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
}
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());
}
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);
}
}
if (!conditionReTestResult)
{
// Cancel the step
Trace.Info("Cancel current running step.");
step.ExecutionContext.CancelToken();
}
});
}
else
{
if (ExecutionContext.Root.Result != TaskResult.Canceled)
{
// Mark job as cancelled
ExecutionContext.Root.Result = TaskResult.Canceled;
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
}
}
// 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);
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
}
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());
}
catch (Exception ex)
{
Trace.Info("Caught exception from expression.");
Trace.Error(ex);
conditionEvaluateError = ex;
}
}
if (!conditionResult && conditionEvaluateError == null)
{
// Condition is false
Trace.Info("Skipping step due to condition evaluation.");
step.ExecutionContext.Result = TaskResult.Skipped;
continue;
}
else if (conditionEvaluateError != null)
{
// Condition error
step.ExecutionContext.Error(conditionEvaluateError);
step.ExecutionContext.Result = TaskResult.Failed;
ExecutionContext.Result = TaskResult.Failed;
break;
}
else
{
await RunStepAsync(step);
}
}
}
finally
{
@@ -409,6 +412,12 @@ namespace GitHub.Runner.Worker.Handlers
{
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value);
// We should run cleanup even if one of the cleanup step fails
if (stage != ActionRunStage.Post)
{
break;
}
}
}
}

View File

@@ -31,8 +31,8 @@ namespace GitHub.Runner.Worker.Handlers
Inputs.TryGetValue("script", out string contents);
contents = contents ?? string.Empty;
if (Action.Type == Pipelines.ActionSourceType.Script)
{
// if (Action.Type == Pipelines.ActionSourceType.Script)
// {
var firstLine = contents.TrimStart(' ', '\t', '\r', '\n');
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
if (firstNewLine >= 0)
@@ -41,11 +41,11 @@ namespace GitHub.Runner.Worker.Handlers
}
ExecutionContext.Output($"##[group]Run {firstLine}");
}
else
{
throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}");
}
// }
// else
// {
// throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}");
// }
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)

View File

@@ -55,7 +55,7 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNull(message, nameof(message));
// Create a new timeline record for 'Set up job'
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Set up job", $"{nameof(JobExtension)}_Init", null, null, ActionRunStage.Pre);
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Set up job", $"{nameof(JobExtension)}_Init", null, null);
List<IStep> preJobSteps = new List<IStep>();
List<IStep> jobSteps = new List<IStep>();
@@ -265,29 +265,91 @@ namespace GitHub.Runner.Worker
if (step.Type == Pipelines.StepType.Action)
{
var action = step as Pipelines.ActionStep;
Trace.Info($"Adding {action.DisplayName}.");
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = action;
actionRunner.Stage = ActionRunStage.Main;
actionRunner.Condition = step.Condition;
var contextData = new Pipelines.ContextData.DictionaryContextData();
if (message.ContextData?.Count > 0)
void EmitStep(Pipelines.ActionStep action)
{
foreach (var pair in message.ContextData)
Trace.Info($"Adding {action.DisplayName}.");
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = action;
actionRunner.Stage = ActionRunStage.Main;
actionRunner.Condition = step.Condition;
var contextData = new Pipelines.ContextData.DictionaryContextData();
if (message.ContextData?.Count > 0)
{
contextData[pair.Key] = pair.Value;
foreach (var pair in message.ContextData)
{
contextData[pair.Key] = pair.Value;
}
}
actionRunner.TryEvaluateDisplayName(contextData, context);
jobSteps.Add(actionRunner);
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
{
Trace.Info($"Adding pre-{action.DisplayName}.");
preStep.TryEvaluateDisplayName(contextData, context);
preStep.DisplayName = $"Pre {preStep.DisplayName}";
preJobSteps.Add(preStep);
}
}
actionRunner.TryEvaluateDisplayName(contextData, context);
jobSteps.Add(actionRunner);
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
// HACK: if the step is "run: make," parse the Makefile and emit $"make dependency {target}"
// Only works for the default target right now.
bool isRunMake = false;
if (action.Reference?.Type == Pipelines.ActionSourceType.Script)
{
Trace.Info($"Adding pre-{action.DisplayName}.");
preStep.TryEvaluateDisplayName(contextData, context);
preStep.DisplayName = $"Pre {preStep.DisplayName}";
preJobSteps.Add(preStep);
var inputs = action.Inputs.AssertMapping(null);
foreach (var pair in inputs)
{
var propertyName = pair.Key.AssertString($"{PipelineTemplateConstants.Steps}");
if (string.Equals(propertyName.Value, "script", StringComparison.OrdinalIgnoreCase))
{
var tokenToParse = pair.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Run}");
if (tokenToParse.ToString() == "make")
{
isRunMake = true;
}
break;
}
}
}
if (isRunMake)
{
// Get the path of the Makefile in the repository root.
var githubContext = jobContext.ExpressionValues["github"] as GitHubContext;
var workspaceDir = githubContext["workspace"] as StringContextData;
var makefile = Path.Combine(workspaceDir, "Makefile");
if (!File.Exists(makefile))
{
// Forget about trying to be smart. Just do the normal thing.
EmitStep(action);
}
else
{
// Assume the default target is named `all`.
var targetDependencies = MakefileReader.ReadTargetDependencies(jobContext, makefile, target: "all");
if (targetDependencies.Count == 0)
{
// Forget about trying to be smart. Just do the normal thing.
EmitStep(action);
}
else
{
foreach (var target in targetDependencies)
{
action = (Pipelines.ActionStep)action.Clone();
action.Id = Guid.NewGuid();
action.DisplayName = $"make dependency {target}";
EmitStep(action);
}
}
}
}
else
{
EmitStep(action);
}
}
}
@@ -306,13 +368,13 @@ namespace GitHub.Runner.Worker
JobExtensionRunner extensionStep = step as JobExtensionRunner;
ArgUtil.NotNull(extensionStep, extensionStep.DisplayName);
Guid stepId = Guid.NewGuid();
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"), ActionRunStage.Pre);
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
}
else if (step is IActionRunner actionStep)
{
ArgUtil.NotNull(actionStep, step.DisplayName);
Guid stepId = Guid.NewGuid();
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, ActionRunStage.Pre, intraActionStates[actionStep.Action.Id]);
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, intraActionStates[actionStep.Action.Id]);
}
}
@@ -323,7 +385,7 @@ namespace GitHub.Runner.Worker
{
ArgUtil.NotNull(actionStep, step.DisplayName);
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, null, actionStep.Action.ContextName, ActionRunStage.Main, intraActionState);
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, null, actionStep.Action.ContextName, intraActionState);
}
}
@@ -394,7 +456,7 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNull(jobContext, nameof(jobContext));
// create a new timeline record node for 'Finalize job'
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post);
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null);
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
{
try

View File

@@ -106,7 +106,6 @@ namespace GitHub.Runner.Worker
}
jobContext.SetRunnerContext("os", VarUtil.OS);
jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture);
var runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
jobContext.SetRunnerContext("name", runnerSettings.AgentName);

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace GitHub.Runner.Worker
{
public static class MakefileReader
{
// Get the dependencies for a target from a Makefile.
// Does not recurse into the dependencies of those dependencies.
public static List<string> ReadTargetDependencies(IExecutionContext executionContext, string makefile, string target)
{
var targetToFind = target + ":";
var lines = File.ReadLines(makefile);
string targetLine = lines.FirstOrDefault(line => line.TrimStart().StartsWith(targetToFind));
if (targetLine is null)
{
return null;
}
return targetLine.Split().Skip(1).ToList();
}
}
}

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>

View File

@@ -123,7 +123,6 @@
"properties": {
"name": "string-steps-context",
"id": "non-empty-string",
"if": "step-if",
"run": {
"type": "string-steps-context",
"required": true
@@ -142,7 +141,6 @@
"properties": {
"name": "string-steps-context",
"id": "non-empty-string",
"if": "step-if",
"uses": {
"type": "non-empty-string",
"required": true
@@ -218,24 +216,6 @@
"loose-value-type": "string"
}
},
"step-if": {
"context": [
"github",
"inputs",
"strategy",
"matrix",
"steps",
"job",
"runner",
"env",
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)",
"hashFiles(1,255)"
],
"string": {}
},
"step-with": {
"context": [
"github",

View File

@@ -28,6 +28,14 @@ namespace GitHub.Services.Common
}
}
public IDictionary<string, object> Properties
{
get
{
return m_request.Properties;
}
}
IEnumerable<String> IHttpHeaders.GetValues(String name)
{
IEnumerable<String> values;

View File

@@ -14,5 +14,10 @@ namespace GitHub.Services.Common
{
get;
}
IDictionary<string, object> Properties
{
get;
}
}
}

View File

@@ -20,11 +20,12 @@ namespace GitHub.Services.Common.Diagnostics
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
{
if (!message.Options.TryGetValue(VssTraceActivity.PropertyName, out VssTraceActivity traceActivity))
Object traceActivity;
if (!message.Properties.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
{
return VssTraceActivity.Empty;
}
return traceActivity;
return (VssTraceActivity)traceActivity;
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Net.Http;
namespace GitHub.Services.Common.Diagnostics
{
@@ -99,7 +98,7 @@ namespace GitHub.Services.Common.Diagnostics
/// <summary>
/// Gets the property name used to cache this object on extensible objects.
/// </summary>
public static readonly HttpRequestOptionsKey<VssTraceActivity> PropertyName = new HttpRequestOptionsKey<VssTraceActivity>("MS.VSS.Diagnostics.TraceActivity");
public const String PropertyName = "MS.VSS.Diagnostics.TraceActivity";
private static Lazy<VssTraceActivity> s_empty = new Lazy<VssTraceActivity>(() => new VssTraceActivity(Guid.Empty));
private sealed class CorrelationScope : IDisposable

View File

@@ -110,7 +110,7 @@ namespace GitHub.Services.Common
}
}
internal static readonly HttpRequestOptionsKey<VssHttpMessageHandler> PropertyName = new HttpRequestOptionsKey<VssHttpMessageHandler>("MS.VS.MessageHandler");
internal static readonly String PropertyName = "MS.VS.MessageHandler";
/// <summary>
/// Handles the authentication hand-shake for a Visual Studio service.
@@ -169,7 +169,7 @@ namespace GitHub.Services.Common
}
// Add ourselves to the message so the underlying token issuers may use it if necessary
request.Options.Set(VssHttpMessageHandler.PropertyName, this);
request.Properties[VssHttpMessageHandler.PropertyName] = this;
Boolean succeeded = false;
Boolean lastResponseDemandedProxyAuth = false;
@@ -409,7 +409,7 @@ namespace GitHub.Services.Common
// Read the completion option provided by the caller. If we don't find the property then we
// assume it is OK to buffer by default.
HttpCompletionOption completionOption;
if (!request.Options.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
if (!request.Properties.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
{
completionOption = HttpCompletionOption.ResponseContentRead;
}

View File

@@ -11,7 +11,7 @@ namespace GitHub.Services.Common
{
DateTime _lastTime;
private static readonly HttpRequestOptionsKey<VssHttpMessageHandlerTraceInfo> TfsTraceInfoKey = new HttpRequestOptionsKey<VssHttpMessageHandlerTraceInfo>("TFS_TraceInfo");
static readonly String TfsTraceInfoKey = "TFS_TraceInfo";
public int TokenRetries { get; internal set; }
@@ -76,9 +76,10 @@ namespace GitHub.Services.Common
/// <param name="traceInfo"></param>
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
{
if (!message.Options.TryGetValue(TfsTraceInfoKey, out var _))
object existingTraceInfo;
if (!message.Properties.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
{
message.Options.Set(TfsTraceInfoKey, traceInfo);
message.Properties.Add(TfsTraceInfoKey, traceInfo);
}
}
@@ -89,8 +90,13 @@ namespace GitHub.Services.Common
/// <returns></returns>
public static VssHttpMessageHandlerTraceInfo GetTraceInfo(HttpRequestMessage message)
{
VssHttpMessageHandlerTraceInfo traceInfo;
message.Options.TryGetValue(TfsTraceInfoKey, out traceInfo);
VssHttpMessageHandlerTraceInfo traceInfo = null;
if (message.Properties.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
{
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
}
return traceInfo;
}

View File

@@ -291,12 +291,12 @@ namespace GitHub.Services.Common
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
{
// Make sure we only apply the settings to the request once
if (request.Options.TryGetValue(PropertyName, out var _))
if (request.Properties.ContainsKey(PropertyName))
{
return false;
}
request.Options.Set(PropertyName, this);
request.Properties.Add(PropertyName, this);
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
{
@@ -366,12 +366,12 @@ namespace GitHub.Services.Common
/// <summary>
/// Gets the property name used to reference this object.
/// </summary>
public static readonly HttpRequestOptionsKey<VssHttpRequestSettings> PropertyName = new HttpRequestOptionsKey<VssHttpRequestSettings>("MS.VS.RequestSettings");
public const String PropertyName = "MS.VS.RequestSettings";
/// <summary>
/// Gets the property name used to reference the completion option for a specific request.
/// </summary>
public static readonly HttpRequestOptionsKey<HttpCompletionOption> HttpCompletionOptionPropertyName = new HttpRequestOptionsKey<HttpCompletionOption>("MS.VS.HttpCompletionOption");
public const String HttpCompletionOptionPropertyName = "MS.VS.HttpCompletionOption";
/// <summary>
/// Header to include the light weight response client option.

View File

@@ -53,14 +53,23 @@ namespace GitHub.Services.Common
VssTraceActivity traceActivity = VssTraceActivity.Current;
// Allow overriding default retry options per request
VssHttpRetryOptions retryOptions;
request.Options.TryGetValue(HttpRetryOptionsKey, out retryOptions);
TimeSpan minBackoff = (retryOptions ?? m_retryOptions).MinBackoff;
Int32 maxAttempts = (retryOptions ?? m_retryOptions).MaxRetries + 1;
VssHttpRetryOptions retryOptions = m_retryOptions;
object retryOptionsObject;
if (request.Properties.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
{
// Fallback to default options if object of unexpected type was passed
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
}
TimeSpan minBackoff = retryOptions.MinBackoff;
Int32 maxAttempts = retryOptions.MaxRetries + 1;
IVssHttpRetryInfo retryInfo = null;
request.Options.TryGetValue(HttpRetryInfoKey, out retryInfo);
object retryInfoObject;
if (request.Properties.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
{
retryInfo = retryInfoObject as IVssHttpRetryInfo;
}
if (IsLowPriority(request))
{
@@ -216,8 +225,8 @@ namespace GitHub.Services.Common
}
private VssHttpRetryOptions m_retryOptions;
public static readonly HttpRequestOptionsKey<IVssHttpRetryInfo> HttpRetryInfoKey = new HttpRequestOptionsKey<IVssHttpRetryInfo>("HttpRetryInfo");
public static readonly HttpRequestOptionsKey<VssHttpRetryOptions> HttpRetryOptionsKey = new HttpRequestOptionsKey<VssHttpRetryOptions>("VssHttpRetryOptions");
public const string HttpRetryInfoKey = "HttpRetryInfo";
public const string HttpRetryOptionsKey = "VssHttpRetryOptions";
private string m_clientName = "";
}
}

View File

@@ -638,7 +638,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Inputs),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<DefineConstants>TRACE</DefineConstants>

View File

@@ -833,7 +833,7 @@ namespace GitHub.Services.WebApi
{
if (userState != null)
{
message.Options.Set(UserStatePropertyName, userState);
message.Properties[UserStatePropertyName] = userState;
}
if (!message.Headers.Contains(Common.Internal.HttpHeaders.VssE2EID))
@@ -842,11 +842,11 @@ namespace GitHub.Services.WebApi
}
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
message.Trace();
message.Options.Set(VssTraceActivity.PropertyName, traceActivity);
message.Properties[VssTraceActivity.PropertyName] = traceActivity;
// Send the completion option to the inner handler stack so we know when it's safe to buffer
// and when we should avoid buffering.
message.Options.Set(VssHttpRequestSettings.HttpCompletionOptionPropertyName, completionOption);
message.Properties[VssHttpRequestSettings.HttpCompletionOptionPropertyName] = completionOption;
//ConfigureAwait(false) enables the continuation to be run outside
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
@@ -1154,9 +1154,7 @@ namespace GitHub.Services.WebApi
{
if (BaseAddress != null)
{
#pragma warning disable SYSLIB0014
ServicePoint servicePoint = ServicePointManager.FindServicePoint(BaseAddress);
#pragma warning restore SYSLIB0014
servicePoint.UseNagleAlgorithm = false;
servicePoint.SetTcpKeepAlive(
enabled: true,
@@ -1274,7 +1272,7 @@ namespace GitHub.Services.WebApi
private const String c_jsonMediaType = "application/json";
public static readonly HttpRequestOptionsKey<object> UserStatePropertyName = new HttpRequestOptionsKey<object>("VssClientBaseUserState");
public readonly static String UserStatePropertyName = "VssClientBaseUserState";
protected sealed class OperationScope : IDisposable
{

View File

@@ -138,7 +138,7 @@ namespace GitHub.Services.WebApi
if (routeReplacementOptions.HasFlag(RouteReplacementOptions.EscapeUri))
{
sbResult = new StringBuilder(Uri.EscapeDataString(sbResult.ToString()));
sbResult = new StringBuilder(Uri.EscapeUriString(sbResult.ToString()));
}
if (routeReplacementOptions.HasFlag(RouteReplacementOptions.AppendUnusedAsQueryParams) && unusedValues.Count > 0)

View File

@@ -6,24 +6,27 @@ using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
namespace GitHub.Services.WebApi
{
internal static class HttpMessageExtensions
{
private static readonly HttpRequestOptionsKey<VssRequestTimerTrace> tracerKey = new HttpRequestOptionsKey<VssRequestTimerTrace>("VSS_HTTP_TIMER_TRACE");
private const string tracerKey = "VSS_HTTP_TIMER_TRACE";
internal static void Trace(this HttpRequestMessage request)
{
VssRequestTimerTrace tracer;
if (request.Options.TryGetValue(tracerKey, out tracer))
Object tracerObj = null;
VssRequestTimerTrace tracer = null;
if (request.Properties.TryGetValue(tracerKey, out tracerObj))
{
tracer = tracerObj as VssRequestTimerTrace;
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
}
else
{
tracer = new VssRequestTimerTrace();
request.Options.Set(tracerKey, tracer);
request.Properties[tracerKey] = tracer;
}
if (tracer != null)
@@ -34,9 +37,11 @@ namespace GitHub.Services.WebApi
internal static void Trace(this HttpResponseMessage response)
{
VssRequestTimerTrace tracer;
if (response.RequestMessage.Options.TryGetValue(tracerKey, out tracer))
Object tracerObj = null;
VssRequestTimerTrace tracer = null;
if (response.RequestMessage.Properties.TryGetValue(tracerKey, out tracerObj))
{
tracer = tracerObj as VssRequestTimerTrace;
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
}

View File

@@ -547,7 +547,6 @@ namespace GitHub.Runner.Common.Tests
}
}
#if OS_WINDOWS
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
@@ -575,7 +574,6 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("some windows logon account", actual);
}
}
#endif
[Fact]
[Trait("Level", "L0")]

View File

@@ -626,32 +626,6 @@ namespace GitHub.Runner.Common.Tests.Worker
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ConditionalCompositeAction()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "conditional_composite_action.yml"));
//Assert
Assert.Equal("Conditional Composite", result.Name);
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
}
finally
{
Teardown();
}
}
[Fact]

View File

@@ -193,9 +193,9 @@ namespace GitHub.Runner.Common.Tests.Worker
// Act.
jobContext.InitializeJob(jobRequest, CancellationToken.None);
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1", "action_1", null, null, 0);
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1", "action_1", null, null);
action1.IntraActionState["state"] = "1";
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null, 0);
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null);
action2.IntraActionState["state"] = "2";
@@ -291,8 +291,8 @@ namespace GitHub.Runner.Common.Tests.Worker
// Act.
jobContext.InitializeJob(jobRequest, CancellationToken.None);
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, 0);
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null, 0);
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null);
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null);
var actionId = Guid.NewGuid();
var postRunner1 = hc.CreateService<IActionRunner>();

View File

@@ -105,36 +105,6 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
}
}
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData(ActionResult.Failure, ActionResult.Failure, true)]
[InlineData(ActionResult.Failure, ActionResult.Success, false)]
[InlineData(ActionResult.Success, ActionResult.Failure, true)]
[InlineData(ActionResult.Success, ActionResult.Success, false)]
[InlineData(ActionResult.Success, null, false)]
public void FailureFunctionComposite(ActionResult jobStatus, ActionResult? actionStatus, bool expected)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange
var executionContext = InitializeExecutionContext(hc);
executionContext.Setup(x => x.GetGitHubContext("action_status")).Returns(actionStatus.ToString());
executionContext.Setup( x=> x.IsEmbedded).Returns(true);
executionContext.Setup( x=> x.Stage).Returns(ActionRunStage.Main);
_jobContext.Status = jobStatus;
// Act.
bool actual = Evaluate("failure()");
// Assert.
Assert.Equal(expected, actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
@@ -164,43 +134,12 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
}
}
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData(ActionResult.Failure, ActionResult.Failure, false)]
[InlineData(ActionResult.Failure, ActionResult.Success, true)]
[InlineData(ActionResult.Success, ActionResult.Failure, false)]
[InlineData(ActionResult.Success, ActionResult.Success, true)]
[InlineData(ActionResult.Success, null, true)]
public void SuccessFunctionComposite(ActionResult jobStatus, ActionResult? actionStatus, bool expected)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange
var executionContext = InitializeExecutionContext(hc);
executionContext.Setup(x => x.GetGitHubContext("action_status")).Returns(actionStatus.ToString());
executionContext.Setup( x=> x.IsEmbedded).Returns(true);
executionContext.Setup( x=> x.Stage).Returns(ActionRunStage.Main);
_jobContext.Status = jobStatus;
// Act.
bool actual = Evaluate("success()");
// Assert.
Assert.Equal(expected, actual);
}
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
return new TestHostContext(this, testName);
}
private Mock<IExecutionContext> InitializeExecutionContext(TestHostContext hc)
private void InitializeExecutionContext(TestHostContext hc)
{
_jobContext = new JobContext();
@@ -210,8 +149,6 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
_templateContext = new TemplateContext();
_templateContext.State[nameof(IExecutionContext)] = executionContext.Object;
return executionContext;
}
private bool Evaluate(string expression)

View File

@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603;NU1603;xUnit2013;</NoWarn>
</PropertyGroup>

View File

@@ -1,49 +0,0 @@
name: 'Conditional Composite'
description: 'Test composite run step conditionals'
inputs:
exit-code:
description: 'Action fails if set to non-zero'
default: '0'
outputs:
default:
description: "Did step run with default?"
value: ${{ steps.default-conditional.outputs.default }}
success:
description: "Did step run with success?"
value: ${{ steps.success-conditional.outputs.success }}
failure:
description: "Did step run with failure?"
value: ${{ steps.failure-conditional.outputs.failure }}
always:
description: "Did step run with always?"
value: ${{ steps.always-conditional.outputs.always }}
runs:
using: "composite"
steps:
- run: exit ${{ inputs.exit-code }}
shell: bash
- run: echo "::set-output name=default::true"
id: default-conditional
shell: bash
- run: echo "::set-output name=success::true"
id: success-conditional
shell: bash
if: success()
- run: echo "::set-output name=failure::true"
id: failure-conditional
shell: bash
if: failure()
- run: echo "::set-output name=always::true"
id: always-conditional
shell: bash
if: always()
- run: echo "failed"
shell: bash
if: ${{ inputs.exit-code == 1 && failure() }}

View File

@@ -14,10 +14,10 @@ DEV_TARGET_RUNTIME=$3
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAYOUT_DIR="$SCRIPT_DIR/../_layout"
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/net6"
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
PACKAGE_DIR="$SCRIPT_DIR/../_package"
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
DOTNETSDK_VERSION="6.0.100"
DOTNETSDK_VERSION="3.1.302"
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
RUNNER_VERSION=$(cat runnerversion)

View File

@@ -1,5 +1,5 @@
{
"sdk": {
"version": "net6"
"version": "3.1.302"
}
}

View File

@@ -1 +1 @@
2.284.0
2.283.3