2019-10-10 00:52:42 -04:00
using System ;
2020-03-18 12:08:51 -04:00
using System.Collections ;
2019-10-10 00:52:42 -04:00
using System.Collections.Generic ;
using System.Collections.Specialized ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
2020-03-18 12:08:51 -04:00
using System.Text ;
2020-07-13 17:23:19 -04:00
using System.Text.RegularExpressions ;
2019-10-10 00:52:42 -04:00
using System.Threading ;
using System.Threading.Tasks ;
using System.Web ;
2020-03-18 12:08:51 -04:00
using GitHub.DistributedTask.Expressions2 ;
2022-04-01 15:18:53 +02:00
using GitHub.DistributedTask.ObjectTemplating.Tokens ;
2019-10-10 00:52:42 -04:00
using GitHub.DistributedTask.Pipelines ;
using GitHub.DistributedTask.Pipelines.ContextData ;
2020-03-03 22:38:19 -05:00
using GitHub.DistributedTask.Pipelines.ObjectTemplating ;
2019-10-10 00:52:42 -04:00
using GitHub.DistributedTask.WebApi ;
using GitHub.Runner.Common ;
2022-02-11 16:18:41 -05:00
using GitHub.Runner.Common.Util ;
2019-10-10 00:52:42 -04:00
using GitHub.Runner.Sdk ;
2020-03-18 12:08:51 -04:00
using GitHub.Runner.Worker.Container ;
using GitHub.Services.WebApi ;
2019-10-10 00:52:42 -04:00
using Newtonsoft.Json ;
2019-10-25 10:38:56 -04:00
using ObjectTemplating = GitHub . DistributedTask . ObjectTemplating ;
using Pipelines = GitHub . DistributedTask . Pipelines ;
2019-10-10 00:52:42 -04:00
namespace GitHub.Runner.Worker
{
public class ExecutionContextType
{
public static string Job = "Job" ;
public static string Task = "Task" ;
}
[ServiceLocator(Default = typeof(ExecutionContext))]
public interface IExecutionContext : IRunnerService
{
Guid Id { get ; }
2021-07-28 15:35:21 -04:00
Guid EmbeddedId { get ; }
2019-10-10 00:52:42 -04:00
string ScopeName { get ; }
2021-08-04 11:39:22 -04:00
string SiblingScopeName { get ; }
2019-10-10 00:52:42 -04:00
string ContextName { get ; }
2021-10-29 15:45:42 +02:00
ActionRunStage Stage { get ; }
2019-10-10 00:52:42 -04:00
Task ForceCompleted { get ; }
TaskResult ? Result { get ; set ; }
2020-03-17 21:18:42 -04:00
TaskResult ? Outcome { get ; set ; }
2019-10-10 00:52:42 -04:00
string ResultCode { get ; set ; }
TaskResult ? CommandResult { get ; set ; }
CancellationToken CancellationToken { get ; }
2020-07-19 19:05:47 -04:00
GlobalContext Global { get ; }
2019-10-10 00:52:42 -04:00
Dictionary < string , string > IntraActionState { get ; }
2020-03-14 17:54:58 -04:00
Dictionary < string , VariableValue > JobOutputs { get ; }
2020-10-21 12:14:21 -04:00
ActionsEnvironmentReference ActionsEnvironment { get ; }
2022-02-11 16:18:41 -05:00
ActionsStepTelemetry StepTelemetry { get ; }
2019-10-10 00:52:42 -04:00
DictionaryContextData ExpressionValues { get ; }
2020-03-18 12:08:51 -04:00
IList < IFunctionInfo > ExpressionFunctions { get ; }
2019-10-10 00:52:42 -04:00
JobContext JobContext { get ; }
// Only job level ExecutionContext has JobSteps
2020-07-29 16:19:04 -04:00
Queue < IStep > JobSteps { get ; }
2019-10-10 00:52:42 -04:00
// Only job level ExecutionContext has PostJobSteps
Stack < IStep > PostJobSteps { get ; }
2021-10-27 09:31:58 -04:00
Dictionary < Guid , string > EmbeddedStepsWithPostRegistered { get ; }
2021-07-28 15:35:21 -04:00
// Keep track of embedded steps states
Dictionary < Guid , Dictionary < string , string > > EmbeddedIntraActionState { get ; }
2019-10-10 00:52:42 -04:00
2019-10-25 10:38:56 -04:00
bool EchoOnActionCommand { get ; set ; }
2021-04-30 15:48:53 -05:00
bool IsEmbedded { get ; }
2020-07-23 09:45:00 -04:00
2020-07-22 18:01:50 -04:00
ExecutionContext Root { get ; }
2019-10-10 00:52:42 -04:00
// Initialize
void InitializeJob ( Pipelines . AgentJobRequestMessage message , CancellationToken token ) ;
void CancelToken ( ) ;
2021-10-29 15:45:42 +02:00
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 ) ;
2019-10-10 00:52:42 -04:00
// logging
long Write ( string tag , string message ) ;
void QueueAttachFile ( string type , string name , string filePath ) ;
// timeline record update methods
void Start ( string currentOperation = null ) ;
TaskResult Complete ( TaskResult ? result = null , string currentOperation = null , string resultCode = null ) ;
void SetEnvContext ( string name , string value ) ;
void SetRunnerContext ( string name , string value ) ;
string GetGitHubContext ( string name ) ;
void SetGitHubContext ( string name , string value ) ;
void SetOutput ( string name , string value , out string reference ) ;
void SetTimeout ( TimeSpan ? timeout ) ;
void AddIssue ( Issue issue , string message = null ) ;
void Progress ( int percentage , string currentOperation = null ) ;
void UpdateDetailTimelineRecord ( TimelineRecord record ) ;
void UpdateTimelineRecordDisplayName ( string displayName ) ;
// matchers
void Add ( OnMatcherChanged handler ) ;
void Remove ( OnMatcherChanged handler ) ;
void AddMatchers ( IssueMatchersConfig matcher ) ;
void RemoveMatchers ( IEnumerable < string > owners ) ;
IEnumerable < IssueMatcherConfig > GetMatchers ( ) ;
// others
void ForceTaskComplete ( ) ;
2020-04-13 21:46:30 -04:00
void RegisterPostJobStep ( IStep step ) ;
2022-02-11 16:18:41 -05:00
void PublishStepTelemetry ( ) ;
2022-04-01 15:18:53 +02:00
void ApplyContinueOnError ( TemplateToken continueOnError ) ;
void UpdateGlobalStepsContext ( ) ;
2022-03-18 02:35:04 +01:00
void WriteWebhookPayload ( ) ;
2022-04-01 15:18:53 +02:00
2019-10-10 00:52:42 -04:00
}
public sealed class ExecutionContext : RunnerService , IExecutionContext
{
private const int _maxIssueCount = 10 ;
2020-05-12 16:09:13 -04:00
private const int _throttlingDelayReportThreshold = 10 * 1000 ; // Don't report throttling with less than 10 seconds delay
2022-02-16 12:18:21 -05:00
private const int _maxIssueMessageLength = 4096 ; // Don't send issue with huge message since we can't forward them from actions to check annotation.
private const int _maxIssueCountInTelemetry = 3 ; // Only send the first 3 issues to telemetry
private const int _maxIssueMessageLengthInTelemetry = 256 ; // Only send the first 256 characters of issue message to telemetry
2019-10-10 00:52:42 -04:00
private readonly TimelineRecord _record = new TimelineRecord ( ) ;
private readonly Dictionary < Guid , TimelineRecord > _detailRecords = new Dictionary < Guid , TimelineRecord > ( ) ;
private readonly object _loggerLock = new object ( ) ;
private readonly object _matchersLock = new object ( ) ;
private event OnMatcherChanged _onMatcherChanged ;
private IssueMatcherConfig [ ] _matchers ;
private IPagingLogger _logger ;
private IJobServerQueue _jobServerQueue ;
private ExecutionContext _parentExecutionContext ;
private Guid _mainTimelineId ;
private Guid _detailTimelineId ;
private bool _expandedForPostJob = false ;
private int _childTimelineRecordOrder = 0 ;
private CancellationTokenSource _cancellationTokenSource ;
private TaskCompletionSource < int > _forceCompleted = new TaskCompletionSource < int > ( ) ;
private bool _throttlingReported = false ;
// only job level ExecutionContext will track throttling delay.
private long _totalThrottlingDelayInMilliseconds = 0 ;
2022-02-11 16:18:41 -05:00
private bool _stepTelemetryPublished = false ;
2019-10-10 00:52:42 -04:00
public Guid Id = > _record . Id ;
2021-07-28 15:35:21 -04:00
public Guid EmbeddedId { get ; private set ; }
2019-10-10 00:52:42 -04:00
public string ScopeName { get ; private set ; }
2021-08-04 11:39:22 -04:00
public string SiblingScopeName { get ; private set ; }
2019-10-10 00:52:42 -04:00
public string ContextName { get ; private set ; }
2021-10-29 15:45:42 +02:00
public ActionRunStage Stage { get ; private set ; }
2019-10-10 00:52:42 -04:00
public Task ForceCompleted = > _forceCompleted . Task ;
public CancellationToken CancellationToken = > _cancellationTokenSource . Token ;
public Dictionary < string , string > IntraActionState { get ; private set ; }
2020-03-14 17:54:58 -04:00
public Dictionary < string , VariableValue > JobOutputs { get ; private set ; }
2020-10-21 12:14:21 -04:00
public ActionsEnvironmentReference ActionsEnvironment { get ; private set ; }
2022-02-11 16:18:41 -05:00
public ActionsStepTelemetry StepTelemetry { get ; } = new ActionsStepTelemetry ( ) ;
2019-10-10 00:52:42 -04:00
public DictionaryContextData ExpressionValues { get ; } = new DictionaryContextData ( ) ;
2020-03-18 12:08:51 -04:00
public IList < IFunctionInfo > ExpressionFunctions { get ; } = new List < IFunctionInfo > ( ) ;
2020-07-19 19:05:47 -04:00
// Shared pointer across job-level execution context and step-level execution contexts
public GlobalContext Global { get ; private set ; }
2019-10-10 00:52:42 -04:00
// Only job level ExecutionContext has JobSteps
2020-07-29 16:19:04 -04:00
public Queue < IStep > JobSteps { get ; private set ; }
2019-10-10 00:52:42 -04:00
// Only job level ExecutionContext has PostJobSteps
public Stack < IStep > PostJobSteps { get ; private set ; }
2020-04-13 21:46:30 -04:00
// Only job level ExecutionContext has StepsWithPostRegistered
public HashSet < Guid > StepsWithPostRegistered { get ; private set ; }
2021-07-28 15:35:21 -04:00
// Only job level ExecutionContext has EmbeddedStepsWithPostRegistered
2021-10-27 09:31:58 -04:00
public Dictionary < Guid , string > EmbeddedStepsWithPostRegistered { get ; private set ; }
2021-07-28 15:35:21 -04:00
public Dictionary < Guid , Dictionary < string , string > > EmbeddedIntraActionState { get ; private set ; }
2019-10-25 10:38:56 -04:00
public bool EchoOnActionCommand { get ; set ; }
2021-04-30 15:48:53 -05:00
// An embedded execution context shares the same record ID, record name, and logger
// as its enclosing execution context.
public bool IsEmbedded { get ; private set ; }
2020-07-23 09:45:00 -04:00
2019-10-10 00:52:42 -04:00
public TaskResult ? Result
{
get
{
return _record . Result ;
}
set
{
_record . Result = value ;
}
}
2020-03-17 21:18:42 -04:00
public TaskResult ? Outcome { get ; set ; }
2019-10-10 00:52:42 -04:00
public TaskResult ? CommandResult { get ; set ; }
private string ContextType = > _record . RecordType ;
public string ResultCode
{
get
{
return _record . ResultCode ;
}
set
{
_record . ResultCode = value ;
}
}
2020-07-22 18:01:50 -04:00
public ExecutionContext Root
2019-10-10 00:52:42 -04:00
{
get
{
var result = this ;
while ( result . _parentExecutionContext ! = null )
{
result = result . _parentExecutionContext ;
}
return result ;
}
}
public JobContext JobContext
{
get
{
return ExpressionValues [ "job" ] as JobContext ;
}
}
public override void Initialize ( IHostContext hostContext )
{
base . Initialize ( hostContext ) ;
_jobServerQueue = HostContext . GetService < IJobServerQueue > ( ) ;
}
public void CancelToken ( )
{
try
{
_cancellationTokenSource . Cancel ( ) ;
}
catch ( ObjectDisposedException e )
{
Trace . Info ( $"Attempted to cancel a disposed token, the execution is already complete: {e.ToString()}" ) ;
}
}
public void ForceTaskComplete ( )
{
Trace . Info ( "Force finish current task in 5 sec." ) ;
Task . Run ( async ( ) = >
{
await Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ;
_forceCompleted ? . TrySetResult ( 1 ) ;
} ) ;
}
2020-04-13 21:46:30 -04:00
public void RegisterPostJobStep ( IStep step )
2019-10-10 00:52:42 -04:00
{
2021-08-04 11:39:22 -04:00
string siblingScopeName = null ;
2021-07-01 13:34:28 -04:00
if ( this . IsEmbedded )
{
2021-10-27 09:31:58 -04:00
if ( step is IActionRunner actionRunner )
2021-07-28 15:35:21 -04:00
{
2021-10-27 09:31:58 -04:00
if ( Root . EmbeddedStepsWithPostRegistered . ContainsKey ( actionRunner . Action . Id ) )
{
Trace . Info ( $"'post' of '{actionRunner.DisplayName}' already push to child post step stack." ) ;
}
2022-02-11 16:18:41 -05:00
else
2021-10-27 09:31:58 -04:00
{
2022-02-11 16:18:41 -05:00
Root . EmbeddedStepsWithPostRegistered [ actionRunner . Action . Id ] = actionRunner . Condition ;
2021-10-27 09:31:58 -04:00
}
return ;
2021-07-28 15:35:21 -04:00
}
2021-10-27 09:31:58 -04:00
}
2021-07-28 15:35:21 -04:00
else if ( step is IActionRunner actionRunner & & ! Root . StepsWithPostRegistered . Add ( actionRunner . Action . Id ) )
2020-04-13 21:46:30 -04:00
{
Trace . Info ( $"'post' of '{actionRunner.DisplayName}' already push to post step stack." ) ;
return ;
}
2021-08-04 11:39:22 -04:00
if ( step is IActionRunner runner )
{
siblingScopeName = runner . Action . ContextName ;
}
2020-04-13 21:46:30 -04:00
2021-08-04 11:39:22 -04:00
step . ExecutionContext = Root . CreatePostChild ( step . DisplayName , IntraActionState , siblingScopeName ) ;
2019-11-04 13:19:21 -05:00
Root . PostJobSteps . Push ( step ) ;
2019-10-10 00:52:42 -04:00
}
2022-02-11 16:18:41 -05:00
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 )
2019-10-10 00:52:42 -04:00
{
Trace . Entering ( ) ;
var child = new ExecutionContext ( ) ;
child . Initialize ( HostContext ) ;
2020-07-19 19:05:47 -04:00
child . Global = Global ;
2019-10-10 00:52:42 -04:00
child . ScopeName = scopeName ;
child . ContextName = contextName ;
2021-10-29 15:45:42 +02:00
child . Stage = stage ;
2021-07-28 15:35:21 -04:00
child . EmbeddedId = embeddedId ;
2021-08-04 11:39:22 -04:00
child . SiblingScopeName = siblingScopeName ;
2019-10-10 00:52:42 -04:00
if ( intraActionState = = null )
{
child . IntraActionState = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
}
else
{
child . IntraActionState = intraActionState ;
}
foreach ( var pair in ExpressionValues )
{
child . ExpressionValues [ pair . Key ] = pair . Value ;
}
2020-03-18 12:08:51 -04:00
foreach ( var item in ExpressionFunctions )
{
child . ExpressionFunctions . Add ( item ) ;
}
2020-07-22 18:01:50 -04:00
child . _cancellationTokenSource = cancellationTokenSource ? ? new CancellationTokenSource ( ) ;
2019-10-10 00:52:42 -04:00
child . _parentExecutionContext = this ;
2019-10-25 10:38:56 -04:00
child . EchoOnActionCommand = EchoOnActionCommand ;
2019-10-10 00:52:42 -04:00
if ( recordOrder ! = null )
{
child . InitializeTimelineRecord ( _mainTimelineId , recordId , _record . Id , ExecutionContextType . Task , displayName , refName , recordOrder ) ;
}
else
{
child . InitializeTimelineRecord ( _mainTimelineId , recordId , _record . Id , ExecutionContextType . Task , displayName , refName , + + _childTimelineRecordOrder ) ;
}
2020-07-13 17:55:15 -04:00
if ( logger ! = null )
{
child . _logger = logger ;
}
else
{
child . _logger = HostContext . CreateService < IPagingLogger > ( ) ;
child . _logger . Setup ( _mainTimelineId , recordId ) ;
}
2019-10-10 00:52:42 -04:00
2021-04-30 15:48:53 -05:00
child . IsEmbedded = isEmbedded ;
2022-02-16 12:18:21 -05:00
child . StepTelemetry . StepId = recordId ;
child . StepTelemetry . Stage = stage . ToString ( ) ;
child . StepTelemetry . IsEmbedded = isEmbedded ;
2020-07-23 09:45:00 -04:00
2019-10-10 00:52:42 -04:00
return child ;
}
2021-04-30 15:48:53 -05:00
/// <summary>
/// An embedded execution context shares the same record ID, record name, logger,
/// and a linked cancellation token.
/// </summary>
2022-02-11 16:18:41 -05:00
public IExecutionContext CreateEmbeddedChild (
string scopeName ,
string contextName ,
Guid embeddedId ,
ActionRunStage stage ,
Dictionary < string , string > intraActionState = null ,
string siblingScopeName = null )
2021-04-30 15:48:53 -05:00
{
2021-10-29 15:45:42 +02:00
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 ) ;
2021-04-30 15:48:53 -05:00
}
2019-10-10 00:52:42 -04:00
public void Start ( string currentOperation = null )
{
_record . CurrentOperation = currentOperation ? ? _record . CurrentOperation ;
_record . StartTime = DateTime . UtcNow ;
_record . State = TimelineRecordState . InProgress ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
public TaskResult Complete ( TaskResult ? result = null , string currentOperation = null , string resultCode = null )
{
if ( result ! = null )
{
Result = result ;
}
// report total delay caused by server throttling.
2020-05-12 16:09:13 -04:00
if ( _totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold )
2019-10-10 00:52:42 -04:00
{
this . Warning ( $"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling." ) ;
}
_record . CurrentOperation = currentOperation ? ? _record . CurrentOperation ;
_record . ResultCode = resultCode ? ? _record . ResultCode ;
_record . FinishTime = DateTime . UtcNow ;
_record . PercentComplete = 100 ;
_record . Result = _record . Result ? ? TaskResult . Succeeded ;
_record . State = TimelineRecordState . Completed ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
// complete all detail timeline records.
if ( _detailTimelineId ! = Guid . Empty & & _detailRecords . Count > 0 )
{
foreach ( var record in _detailRecords )
{
record . Value . FinishTime = record . Value . FinishTime ? ? DateTime . UtcNow ;
record . Value . PercentComplete = record . Value . PercentComplete ? ? 100 ;
record . Value . Result = record . Value . Result ? ? TaskResult . Succeeded ;
record . Value . State = TimelineRecordState . Completed ;
_jobServerQueue . QueueTimelineRecordUpdate ( _detailTimelineId , record . Value ) ;
}
}
2022-02-11 16:18:41 -05:00
PublishStepTelemetry ( ) ;
2020-05-21 11:09:50 -04:00
if ( Root ! = this )
{
2020-10-21 12:14:21 -04:00
// only dispose TokenSource for step level ExecutionContext
2020-05-21 11:09:50 -04:00
_cancellationTokenSource ? . Dispose ( ) ;
}
2019-10-10 00:52:42 -04:00
_logger . End ( ) ;
2022-04-01 15:18:53 +02:00
UpdateGlobalStepsContext ( ) ;
return Result . Value ;
}
public void UpdateGlobalStepsContext ( )
{
2021-07-28 15:35:21 -04:00
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
2020-08-04 11:12:40 -04:00
if ( ! string . IsNullOrEmpty ( ContextName ) & & ! ContextName . StartsWith ( "__" , StringComparison . Ordinal ) )
2020-03-17 21:18:42 -04:00
{
2020-07-19 19:05:47 -04:00
Global . StepsContext . SetOutcome ( ScopeName , ContextName , ( Outcome ? ? Result ? ? TaskResult . Succeeded ) . ToActionResult ( ) ) ;
Global . StepsContext . SetConclusion ( ScopeName , ContextName , ( Result ? ? TaskResult . Succeeded ) . ToActionResult ( ) ) ;
2020-03-17 21:18:42 -04:00
}
2019-10-10 00:52:42 -04:00
}
public void SetRunnerContext ( string name , string value )
{
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
var runnerContext = ExpressionValues [ "runner" ] as RunnerContext ;
runnerContext [ name ] = new StringContextData ( value ) ;
}
public void SetEnvContext ( string name , string value )
{
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
#if OS_WINDOWS
var envContext = ExpressionValues [ "env" ] as DictionaryContextData ;
envContext [ name ] = new StringContextData ( value ) ;
#else
var envContext = ExpressionValues [ "env" ] as CaseSensitiveDictionaryContextData ;
envContext [ name ] = new StringContextData ( value ) ;
#endif
}
public void SetGitHubContext ( string name , string value )
{
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
var githubContext = ExpressionValues [ "github" ] as GitHubContext ;
githubContext [ name ] = new StringContextData ( value ) ;
}
public string GetGitHubContext ( string name )
{
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
var githubContext = ExpressionValues [ "github" ] as GitHubContext ;
if ( githubContext . TryGetValue ( name , out var value ) )
{
if ( value is StringContextData )
{
return value as StringContextData ;
}
else
{
return value . ToJToken ( ) . ToString ( Formatting . Indented ) ;
}
}
else
{
return null ;
}
}
public void SetOutput ( string name , string value , out string reference )
{
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
2021-07-28 15:35:21 -04:00
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
2020-08-04 11:12:40 -04:00
if ( string . IsNullOrEmpty ( ContextName ) | | ContextName . StartsWith ( "__" , StringComparison . Ordinal ) )
2019-10-10 00:52:42 -04:00
{
reference = null ;
return ;
}
// todo: restrict multiline?
2020-07-19 19:05:47 -04:00
Global . StepsContext . SetOutput ( ScopeName , ContextName , name , value , out reference ) ;
2019-10-10 00:52:42 -04:00
}
public void SetTimeout ( TimeSpan ? timeout )
{
if ( timeout ! = null )
{
_cancellationTokenSource . CancelAfter ( timeout . Value ) ;
}
}
public void Progress ( int percentage , string currentOperation = null )
{
if ( percentage > 100 | | percentage < 0 )
{
throw new ArgumentOutOfRangeException ( nameof ( percentage ) ) ;
}
_record . CurrentOperation = currentOperation ? ? _record . CurrentOperation ;
_record . PercentComplete = Math . Max ( percentage , _record . PercentComplete . Value ) ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
// This is not thread safe, the caller need to take lock before calling issue()
public void AddIssue ( Issue issue , string logMessage = null )
{
ArgUtil . NotNull ( issue , nameof ( issue ) ) ;
if ( string . IsNullOrEmpty ( logMessage ) )
{
logMessage = issue . Message ;
}
issue . Message = HostContext . SecretMasker . MaskSecrets ( issue . Message ) ;
2022-02-16 12:18:21 -05:00
if ( issue . Message . Length > _maxIssueMessageLength )
{
issue . Message = issue . Message [ . . _maxIssueMessageLength ] ;
}
2019-10-10 00:52:42 -04:00
2022-03-14 11:20:11 -04:00
// Tracking the line number (logFileLineNumber) and step number (stepNumber) for each issue that gets created
// Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from
if ( _record . Order ! = null )
{
issue . Data [ "stepNumber" ] = _record . Order . ToString ( ) ;
}
2019-10-10 00:52:42 -04:00
if ( issue . Type = = IssueType . Error )
{
if ( ! string . IsNullOrEmpty ( logMessage ) )
{
long logLineNumber = Write ( WellKnownTags . Error , logMessage ) ;
issue . Data [ "logFileLineNumber" ] = logLineNumber . ToString ( ) ;
}
if ( _record . ErrorCount < _maxIssueCount )
{
_record . Issues . Add ( issue ) ;
}
_record . ErrorCount + + ;
}
else if ( issue . Type = = IssueType . Warning )
{
if ( ! string . IsNullOrEmpty ( logMessage ) )
{
long logLineNumber = Write ( WellKnownTags . Warning , logMessage ) ;
issue . Data [ "logFileLineNumber" ] = logLineNumber . ToString ( ) ;
}
if ( _record . WarningCount < _maxIssueCount )
{
_record . Issues . Add ( issue ) ;
}
_record . WarningCount + + ;
2021-10-27 09:31:58 -04:00
}
else if ( issue . Type = = IssueType . Notice )
2021-07-13 11:38:16 -04:00
{
if ( ! string . IsNullOrEmpty ( logMessage ) )
{
long logLineNumber = Write ( WellKnownTags . Notice , logMessage ) ;
issue . Data [ "logFileLineNumber" ] = logLineNumber . ToString ( ) ;
}
if ( _record . NoticeCount < _maxIssueCount )
{
_record . Issues . Add ( issue ) ;
}
_record . NoticeCount + + ;
2019-10-10 00:52:42 -04:00
}
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
public void UpdateDetailTimelineRecord ( TimelineRecord record )
{
ArgUtil . NotNull ( record , nameof ( record ) ) ;
if ( record . RecordType = = ExecutionContextType . Job )
{
throw new ArgumentOutOfRangeException ( nameof ( record ) ) ;
}
if ( _detailTimelineId = = Guid . Empty )
{
// create detail timeline
_detailTimelineId = Guid . NewGuid ( ) ;
_record . Details = new Timeline ( _detailTimelineId ) ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
TimelineRecord existRecord ;
if ( _detailRecords . TryGetValue ( record . Id , out existRecord ) )
{
existRecord . Name = record . Name ? ? existRecord . Name ;
existRecord . RecordType = record . RecordType ? ? existRecord . RecordType ;
existRecord . Order = record . Order ? ? existRecord . Order ;
existRecord . ParentId = record . ParentId ? ? existRecord . ParentId ;
existRecord . StartTime = record . StartTime ? ? existRecord . StartTime ;
existRecord . FinishTime = record . FinishTime ? ? existRecord . FinishTime ;
existRecord . PercentComplete = record . PercentComplete ? ? existRecord . PercentComplete ;
existRecord . CurrentOperation = record . CurrentOperation ? ? existRecord . CurrentOperation ;
existRecord . Result = record . Result ? ? existRecord . Result ;
existRecord . ResultCode = record . ResultCode ? ? existRecord . ResultCode ;
existRecord . State = record . State ? ? existRecord . State ;
_jobServerQueue . QueueTimelineRecordUpdate ( _detailTimelineId , existRecord ) ;
}
else
{
_detailRecords [ record . Id ] = record ;
_jobServerQueue . QueueTimelineRecordUpdate ( _detailTimelineId , record ) ;
}
}
public void UpdateTimelineRecordDisplayName ( string displayName )
{
ArgUtil . NotNull ( displayName , nameof ( displayName ) ) ;
_record . Name = displayName ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
public void InitializeJob ( Pipelines . AgentJobRequestMessage message , CancellationToken token )
{
// Validation
Trace . Entering ( ) ;
ArgUtil . NotNull ( message , nameof ( message ) ) ;
ArgUtil . NotNull ( message . Resources , nameof ( message . Resources ) ) ;
ArgUtil . NotNull ( message . Variables , nameof ( message . Variables ) ) ;
ArgUtil . NotNull ( message . Plan , nameof ( message . Plan ) ) ;
_cancellationTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( token ) ;
2020-07-19 19:05:47 -04:00
Global = new GlobalContext ( ) ;
2020-06-09 08:53:28 -04:00
// Plan
2020-07-19 19:05:47 -04:00
Global . Plan = message . Plan ;
Global . Features = PlanUtil . GetFeatures ( message . Plan ) ;
2019-10-10 00:52:42 -04:00
// Endpoints
2020-07-19 19:05:47 -04:00
Global . Endpoints = message . Resources . Endpoints ;
2019-10-10 00:52:42 -04:00
// Variables
2020-07-19 19:05:47 -04:00
Global . Variables = new Variables ( HostContext , message . Variables ) ;
2019-10-10 00:52:42 -04:00
2022-02-14 15:06:08 +01:00
if ( Global . Variables . GetBoolean ( "DistributedTask.ForceInternalNodeVersionOnRunnerTo12" ) ? ? false )
{
2022-02-25 20:59:02 +01:00
Environment . SetEnvironmentVariable ( Constants . Variables . Agent . ForcedInternalNodeVersion , "node12" ) ;
2022-02-14 15:06:08 +01:00
}
2019-10-10 00:52:42 -04:00
// Environment variables shared across all actions
2020-07-19 19:05:47 -04:00
Global . EnvironmentVariables = new Dictionary < string , string > ( VarUtil . EnvironmentVariableKeyComparer ) ;
2019-10-10 00:52:42 -04:00
2020-03-17 23:40:37 -04:00
// Job defaults shared across all actions
2020-07-19 19:05:47 -04:00
Global . JobDefaults = new Dictionary < string , IDictionary < string , string > > ( StringComparer . OrdinalIgnoreCase ) ;
2020-03-17 23:40:37 -04:00
2022-02-11 16:18:41 -05:00
// Job Telemetry
Global . JobTelemetry = new List < JobTelemetry > ( ) ;
// ActionsStepTelemetry for entire job
Global . StepsTelemetry = new List < ActionsStepTelemetry > ( ) ;
2020-03-14 17:54:58 -04:00
// Job Outputs
JobOutputs = new Dictionary < string , VariableValue > ( StringComparer . OrdinalIgnoreCase ) ;
2020-10-21 12:14:21 -04:00
// Actions environment
ActionsEnvironment = message . ActionsEnvironment ;
2021-09-20 14:44:50 +02:00
2019-10-10 00:52:42 -04:00
// Service container info
2020-07-19 19:05:47 -04:00
Global . ServiceContainers = new List < ContainerInfo > ( ) ;
2019-10-10 00:52:42 -04:00
// Steps context (StepsRunner manages adding the scoped steps context)
2020-07-19 19:05:47 -04:00
Global . StepsContext = new StepsContext ( ) ;
2019-10-10 00:52:42 -04:00
2020-03-03 22:38:19 -05:00
// File table
2020-07-19 19:05:47 -04:00
Global . FileTable = new List < String > ( message . FileTable ? ? new string [ 0 ] ) ;
2020-03-03 22:38:19 -05:00
2019-10-10 00:52:42 -04:00
// Expression values
if ( message . ContextData ? . Count > 0 )
{
foreach ( var pair in message . ContextData )
{
ExpressionValues [ pair . Key ] = pair . Value ;
}
}
2020-07-19 19:05:47 -04:00
ExpressionValues [ "secrets" ] = Global . Variables . ToSecretsContext ( ) ;
2019-10-10 00:52:42 -04:00
ExpressionValues [ "runner" ] = new RunnerContext ( ) ;
ExpressionValues [ "job" ] = new JobContext ( ) ;
Trace . Info ( "Initialize GitHub context" ) ;
2020-07-19 19:05:47 -04:00
var githubAccessToken = new StringContextData ( Global . Variables . Get ( "system.github.token" ) ) ;
2019-10-10 00:52:42 -04:00
var base64EncodedToken = Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( $"x-access-token:{githubAccessToken}" ) ) ;
HostContext . SecretMasker . AddValue ( base64EncodedToken ) ;
2020-07-19 19:05:47 -04:00
var githubJob = Global . Variables . Get ( "system.github.job" ) ;
2019-10-10 00:52:42 -04:00
var githubContext = new GitHubContext ( ) ;
githubContext [ "token" ] = githubAccessToken ;
2020-03-12 20:47:25 -04:00
if ( ! string . IsNullOrEmpty ( githubJob ) )
{
githubContext [ "job" ] = new StringContextData ( githubJob ) ;
}
2019-10-10 00:52:42 -04:00
var githubDictionary = ExpressionValues [ "github" ] . AssertDictionary ( "github" ) ;
foreach ( var pair in githubDictionary )
{
githubContext [ pair . Key ] = pair . Value ;
}
ExpressionValues [ "github" ] = githubContext ;
Trace . Info ( "Initialize Env context" ) ;
#if OS_WINDOWS
ExpressionValues [ "env" ] = new DictionaryContextData ( ) ;
#else
ExpressionValues [ "env" ] = new CaseSensitiveDictionaryContextData ( ) ;
#endif
// Prepend Path
2020-07-19 19:05:47 -04:00
Global . PrependPath = new List < string > ( ) ;
2019-10-10 00:52:42 -04:00
// JobSteps for job ExecutionContext
2020-07-29 16:19:04 -04:00
JobSteps = new Queue < IStep > ( ) ;
2019-10-10 00:52:42 -04:00
// PostJobSteps for job ExecutionContext
PostJobSteps = new Stack < IStep > ( ) ;
2020-04-13 21:46:30 -04:00
// StepsWithPostRegistered for job ExecutionContext
StepsWithPostRegistered = new HashSet < Guid > ( ) ;
2021-07-28 15:35:21 -04:00
// EmbeddedStepsWithPostRegistered for job ExecutionContext
2021-10-27 09:31:58 -04:00
EmbeddedStepsWithPostRegistered = new Dictionary < Guid , string > ( ) ;
2021-07-28 15:35:21 -04:00
// EmbeddedIntraActionState for job ExecutionContext
2021-10-27 09:31:58 -04:00
EmbeddedIntraActionState = new Dictionary < Guid , Dictionary < string , string > > ( ) ;
2021-07-28 15:35:21 -04:00
2019-10-10 00:52:42 -04:00
// Job timeline record.
InitializeTimelineRecord (
timelineId : message . Timeline . Id ,
timelineRecordId : message . JobId ,
parentTimelineRecordId : null ,
recordType : ExecutionContextType . Job ,
displayName : message . JobDisplayName ,
refName : message . JobName ,
order : null ) ; // The job timeline record's order is set by server.
// Logger (must be initialized before writing warnings).
_logger = HostContext . CreateService < IPagingLogger > ( ) ;
_logger . Setup ( _mainTimelineId , _record . Id ) ;
2019-10-25 10:38:56 -04:00
// Initialize 'echo on action command success' property, default to false, unless Step_Debug is set
2020-07-19 19:05:47 -04:00
EchoOnActionCommand = Global . Variables . Step_Debug ? ? false ;
2019-10-25 10:38:56 -04:00
2019-10-10 00:52:42 -04:00
// Verbosity (from GitHub.Step_Debug).
2020-07-19 19:05:47 -04:00
Global . WriteDebug = Global . Variables . Step_Debug ? ? false ;
2019-10-10 00:52:42 -04:00
// Hook up JobServerQueueThrottling event, we will log warning on server tarpit.
_jobServerQueue . JobServerQueueThrottling + = JobServerQueueThrottling_EventReceived ;
}
// Do not add a format string overload. In general, execution context messages are user facing and
// therefore should be localized. Use the Loc methods from the StringUtil class. The exception to
// the rule is command messages - which should be crafted using strongly typed wrapper methods.
public long Write ( string tag , string message )
{
string msg = HostContext . SecretMasker . MaskSecrets ( $"{tag}{message}" ) ;
long totalLines ;
lock ( _loggerLock )
{
totalLines = _logger . TotalLines + 1 ;
_logger . Write ( msg ) ;
}
// write to job level execution context's log file.
if ( _parentExecutionContext ! = null )
{
lock ( _parentExecutionContext . _loggerLock )
{
_parentExecutionContext . _logger . Write ( msg ) ;
}
}
2020-08-25 12:02:29 -04:00
_jobServerQueue . QueueWebConsoleLine ( _record . Id , msg , totalLines ) ;
2019-10-10 00:52:42 -04:00
return totalLines ;
}
public void QueueAttachFile ( string type , string name , string filePath )
{
ArgUtil . NotNullOrEmpty ( type , nameof ( type ) ) ;
ArgUtil . NotNullOrEmpty ( name , nameof ( name ) ) ;
ArgUtil . NotNullOrEmpty ( filePath , nameof ( filePath ) ) ;
if ( ! File . Exists ( filePath ) )
{
throw new FileNotFoundException ( $"Can't attach (type:{type} name:{name}) file: {filePath}. File does not exist." ) ;
}
_jobServerQueue . QueueFileUpload ( _mainTimelineId , _record . Id , type , name , filePath , deleteSource : false ) ;
}
// Add OnMatcherChanged
public void Add ( OnMatcherChanged handler )
{
Root . _onMatcherChanged + = handler ;
}
// Remove OnMatcherChanged
public void Remove ( OnMatcherChanged handler )
{
Root . _onMatcherChanged - = handler ;
}
// Add Issue matchers
public void AddMatchers ( IssueMatchersConfig config )
{
var root = Root ;
// Lock
lock ( root . _matchersLock )
{
var newMatchers = new List < IssueMatcherConfig > ( ) ;
// Prepend
var newOwners = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
foreach ( var matcher in config . Matchers )
{
newOwners . Add ( matcher . Owner ) ;
newMatchers . Add ( matcher ) ;
}
// Add existing non-matching
var existingMatchers = root . _matchers ? ? Array . Empty < IssueMatcherConfig > ( ) ;
newMatchers . AddRange ( existingMatchers . Where ( x = > ! newOwners . Contains ( x . Owner ) ) ) ;
// Store
root . _matchers = newMatchers . ToArray ( ) ;
// Fire events
foreach ( var matcher in config . Matchers )
{
root . _onMatcherChanged ( null , new MatcherChangedEventArgs ( matcher ) ) ;
}
// Output
var owners = config . Matchers . Select ( x = > $"'{x.Owner}'" ) ;
var joinedOwners = string . Join ( ", " , owners ) ;
// todo: loc
2020-03-12 02:52:46 +01:00
this . Debug ( $"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline." ) ;
2019-10-10 00:52:42 -04:00
}
}
// Remove issue matcher
public void RemoveMatchers ( IEnumerable < string > owners )
{
var root = Root ;
var distinctOwners = new HashSet < string > ( owners , StringComparer . OrdinalIgnoreCase ) ;
var removedMatchers = new List < IssueMatcherConfig > ( ) ;
var newMatchers = new List < IssueMatcherConfig > ( ) ;
// Lock
lock ( root . _matchersLock )
{
// Remove
var existingMatchers = root . _matchers ? ? Array . Empty < IssueMatcherConfig > ( ) ;
foreach ( var matcher in existingMatchers )
{
if ( distinctOwners . Contains ( matcher . Owner ) )
{
removedMatchers . Add ( matcher ) ;
}
else
{
newMatchers . Add ( matcher ) ;
}
}
// Store
root . _matchers = newMatchers . ToArray ( ) ;
// Fire events
foreach ( var removedMatcher in removedMatchers )
{
root . _onMatcherChanged ( null , new MatcherChangedEventArgs ( new IssueMatcherConfig { Owner = removedMatcher . Owner } ) ) ;
}
// Output
owners = removedMatchers . Select ( x = > $"'{x.Owner}'" ) ;
var joinedOwners = string . Join ( ", " , owners ) ;
// todo: loc
2020-03-12 02:52:46 +01:00
this . Debug ( $"Removed matchers: {joinedOwners}" ) ;
2019-10-10 00:52:42 -04:00
}
}
// Get issue matchers
public IEnumerable < IssueMatcherConfig > GetMatchers ( )
{
// Lock not required since the list is immutable
return Root . _matchers ? ? Array . Empty < IssueMatcherConfig > ( ) ;
}
2022-02-11 16:18:41 -05:00
public void PublishStepTelemetry ( )
{
if ( ! _stepTelemetryPublished )
{
// Add to the global steps telemetry only if we have something to log.
if ( ! string . IsNullOrEmpty ( StepTelemetry ? . Type ) )
{
2022-02-16 12:18:21 -05:00
if ( ! IsEmbedded )
{
StepTelemetry . Result = _record . Result ;
}
if ( ! IsEmbedded & &
_record . FinishTime ! = null & &
_record . StartTime ! = null )
{
StepTelemetry . ExecutionTimeInSeconds = ( int ) Math . Ceiling ( ( _record . FinishTime - _record . StartTime ) ? . TotalSeconds ? ? 0 ) ;
}
if ( ! IsEmbedded & &
_record . Issues . Count > 0 )
{
foreach ( var issue in _record . Issues )
{
if ( ( issue . Type = = IssueType . Error | | issue . Type = = IssueType . Warning ) & &
! string . IsNullOrEmpty ( issue . Message ) )
{
string issueTelemetry ;
if ( issue . Message . Length > _maxIssueMessageLengthInTelemetry )
{
issueTelemetry = $"{issue.Message[.._maxIssueMessageLengthInTelemetry]}" ;
}
else
{
issueTelemetry = issue . Message ;
}
StepTelemetry . ErrorMessages . Add ( issueTelemetry ) ;
// Only send over the first 3 issues to avoid sending too much data.
if ( StepTelemetry . ErrorMessages . Count > = _maxIssueCountInTelemetry )
{
break ;
}
}
}
}
2022-02-11 16:18:41 -05:00
Trace . Info ( $"Publish step telemetry for current step {StringUtil.ConvertToJson(StepTelemetry)}." ) ;
Global . StepsTelemetry . Add ( StepTelemetry ) ;
_stepTelemetryPublished = true ;
}
}
else
{
Trace . Info ( $"Step telemetry has already been published." ) ;
}
}
2022-03-18 02:35:04 +01:00
public void WriteWebhookPayload ( )
{
// Makes directory for event_path data
var tempDirectory = HostContext . GetDirectory ( WellKnownDirectory . Temp ) ;
var workflowDirectory = Path . Combine ( tempDirectory , "_github_workflow" ) ;
Directory . CreateDirectory ( workflowDirectory ) ;
var gitHubEvent = GetGitHubContext ( "event" ) ;
// adds the GitHub event path/file if the event exists
if ( gitHubEvent ! = null )
{
var workflowFile = Path . Combine ( workflowDirectory , "event.json" ) ;
Trace . Info ( $"Write event payload to {workflowFile}" ) ;
File . WriteAllText ( workflowFile , gitHubEvent , new UTF8Encoding ( false ) ) ;
SetGitHubContext ( "event_path" , workflowFile ) ;
}
}
2019-10-10 00:52:42 -04:00
private void InitializeTimelineRecord ( Guid timelineId , Guid timelineRecordId , Guid ? parentTimelineRecordId , string recordType , string displayName , string refName , int? order )
{
_mainTimelineId = timelineId ;
_record . Id = timelineRecordId ;
_record . RecordType = recordType ;
_record . Name = displayName ;
_record . RefName = refName ;
_record . Order = order ;
_record . PercentComplete = 0 ;
_record . State = TimelineRecordState . Pending ;
_record . ErrorCount = 0 ;
_record . WarningCount = 0 ;
2021-07-13 11:38:16 -04:00
_record . NoticeCount = 0 ;
2019-10-10 00:52:42 -04:00
if ( parentTimelineRecordId ! = null & & parentTimelineRecordId . Value ! = Guid . Empty )
{
_record . ParentId = parentTimelineRecordId ;
}
2021-01-25 11:14:28 -05:00
else if ( parentTimelineRecordId = = null )
{
_record . AgentPlatform = VarUtil . OS ;
}
2019-10-10 00:52:42 -04:00
var configuration = HostContext . GetService < IConfigurationStore > ( ) ;
_record . WorkerName = configuration . GetSettings ( ) . AgentName ;
_jobServerQueue . QueueTimelineRecordUpdate ( _mainTimelineId , _record ) ;
}
private void JobServerQueueThrottling_EventReceived ( object sender , ThrottlingEventArgs data )
{
Interlocked . Add ( ref _totalThrottlingDelayInMilliseconds , Convert . ToInt64 ( data . Delay . TotalMilliseconds ) ) ;
2020-05-21 11:09:50 -04:00
if ( ! _throttlingReported & &
2020-05-12 16:09:13 -04:00
_totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold )
2019-10-10 00:52:42 -04:00
{
this . Warning ( string . Format ( "The job is currently being throttled by the server. You may experience delays in console line output, job status reporting, and action log uploads." ) ) ;
_throttlingReported = true ;
}
}
2021-08-04 11:39:22 -04:00
private IExecutionContext CreatePostChild ( string displayName , Dictionary < string , string > intraActionState , string siblingScopeName = null )
2019-10-10 00:52:42 -04:00
{
if ( ! _expandedForPostJob )
{
Trace . Info ( $"Reserve record order {_childTimelineRecordOrder + 1} to {_childTimelineRecordOrder * 2} for post job actions." ) ;
_expandedForPostJob = true ;
_childTimelineRecordOrder = _childTimelineRecordOrder * 2 ;
}
2020-04-13 21:46:30 -04:00
var newGuid = Guid . NewGuid ( ) ;
2021-10-29 15:45:42 +02:00
return CreateChild ( newGuid , displayName , newGuid . ToString ( "N" ) , null , null , ActionRunStage . Post , intraActionState , _childTimelineRecordOrder - Root . PostJobSteps . Count , siblingScopeName : siblingScopeName ) ;
2019-10-10 00:52:42 -04:00
}
2022-04-01 15:18:53 +02:00
public void ApplyContinueOnError ( TemplateToken continueOnErrorToken )
{
if ( Result ! = TaskResult . Failed )
{
return ;
}
var continueOnError = false ;
try
{
var templateEvaluator = this . ToPipelineTemplateEvaluator ( ) ;
continueOnError = templateEvaluator . EvaluateStepContinueOnError ( continueOnErrorToken , ExpressionValues , ExpressionFunctions ) ;
}
catch ( Exception ex )
{
Trace . Info ( "The step failed and an error occurred when attempting to determine whether to continue on error." ) ;
Trace . Error ( ex ) ;
this . Error ( "The step failed and an error occurred when attempting to determine whether to continue on error." ) ;
this . Error ( ex ) ;
}
if ( continueOnError )
{
Outcome = Result ;
Result = TaskResult . Succeeded ;
Trace . Info ( $"Updated step result (continue on error)" ) ;
}
UpdateGlobalStepsContext ( ) ;
}
2019-10-10 00:52:42 -04:00
}
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
// Otherwise individual overloads would need to be implemented (depending on the unit test).
public static class ExecutionContextExtension
{
2020-07-19 17:19:13 -04:00
public static string GetFullyQualifiedContextName ( this IExecutionContext context )
{
if ( ! string . IsNullOrEmpty ( context . ScopeName ) )
{
return $"{context.ScopeName}.{context.ContextName}" ;
}
return context . ContextName ;
}
2019-10-10 00:52:42 -04:00
public static void Error ( this IExecutionContext context , Exception ex )
{
context . Error ( ex . Message ) ;
context . Debug ( ex . ToString ( ) ) ;
}
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Error ( this IExecutionContext context , string message )
{
context . AddIssue ( new Issue ( ) { Type = IssueType . Error , Message = message } ) ;
}
2020-12-11 11:07:43 -05:00
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void InfrastructureError ( this IExecutionContext context , string message )
{
2021-10-27 09:31:58 -04:00
context . AddIssue ( new Issue ( ) { Type = IssueType . Error , Message = message , IsInfrastructureIssue = true } ) ;
2020-12-11 11:07:43 -05:00
}
2019-10-10 00:52:42 -04:00
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Warning ( this IExecutionContext context , string message )
{
context . AddIssue ( new Issue ( ) { Type = IssueType . Warning , Message = message } ) ;
}
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Output ( this IExecutionContext context , string message )
{
context . Write ( null , message ) ;
}
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Command ( this IExecutionContext context , string message )
{
context . Write ( WellKnownTags . Command , message ) ;
}
//
// Verbose output is enabled by setting ACTIONS_STEP_DEBUG
// It's meant to help the end user debug their definitions.
// Why are my inputs not working? It's not meant for dev debugging which is diag
//
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Debug ( this IExecutionContext context , string message )
{
2020-07-19 19:05:47 -04:00
if ( context . Global . WriteDebug )
2019-10-10 00:52:42 -04:00
{
var multilines = message ? . Replace ( "\r\n" , "\n" ) ? . Split ( "\n" ) ;
if ( multilines ! = null )
{
foreach ( var line in multilines )
{
context . Write ( WellKnownTags . Debug , line ) ;
}
}
}
}
2020-03-18 12:08:51 -04:00
public static IEnumerable < KeyValuePair < string , object > > ToExpressionState ( this IExecutionContext context )
2020-03-03 22:38:19 -05:00
{
2020-03-18 12:08:51 -04:00
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 ( ) ;
2021-04-06 15:45:40 -05:00
return new PipelineTemplateEvaluator ( traceWriter , schema , context . Global . FileTable )
{
MaxErrorMessageLength = int . MaxValue , // Don't truncate error messages otherwise we might not scrub secrets correctly
} ;
2020-03-03 22:38:19 -05:00
}
2019-10-10 00:52:42 -04:00
public static ObjectTemplating . ITraceWriter ToTemplateTraceWriter ( this IExecutionContext context )
{
return new TemplateTraceWriter ( context ) ;
}
}
internal sealed class TemplateTraceWriter : ObjectTemplating . ITraceWriter
{
private readonly IExecutionContext _executionContext ;
internal TemplateTraceWriter ( IExecutionContext executionContext )
{
2020-03-18 12:08:51 -04:00
ArgUtil . NotNull ( executionContext , nameof ( executionContext ) ) ;
2019-10-10 00:52:42 -04:00
_executionContext = executionContext ;
}
public void Error ( string format , params Object [ ] args )
{
_executionContext . Error ( string . Format ( CultureInfo . CurrentCulture , format , args ) ) ;
}
public void Info ( string format , params Object [ ] args )
{
_executionContext . Debug ( string . Format ( CultureInfo . CurrentCulture , $"{format}" , args ) ) ;
}
public void Verbose ( string format , params Object [ ] args )
{
// todo: switch to verbose?
_executionContext . Debug ( string . Format ( CultureInfo . CurrentCulture , $"{format}" , args ) ) ;
}
}
public static class WellKnownTags
{
public static readonly string Section = "##[section]" ;
public static readonly string Command = "##[command]" ;
public static readonly string Error = "##[error]" ;
public static readonly string Warning = "##[warning]" ;
2021-07-13 11:38:16 -04:00
public static readonly string Notice = "##[notice]" ;
2019-10-10 00:52:42 -04:00
public static readonly string Debug = "##[debug]" ;
}
}