2023-06-02 21:47:59 +02:00
using GitHub.DistributedTask.Pipelines.ContextData ;
2019-10-10 00:52:42 -04:00
using GitHub.DistributedTask.WebApi ;
2020-02-12 21:07:43 -05:00
using GitHub.Runner.Worker.Container ;
2019-10-10 00:52:42 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using GitHub.Runner.Common ;
using GitHub.Runner.Sdk ;
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(ActionCommandManager))]
public interface IActionCommandManager : IRunnerService
{
void EnablePluginInternalCommand ( ) ;
void DisablePluginInternalCommand ( ) ;
2020-02-12 21:07:43 -05:00
bool TryProcessCommand ( IExecutionContext context , string input , ContainerInfo container ) ;
2019-10-10 00:52:42 -04:00
}
public sealed class ActionCommandManager : RunnerService , IActionCommandManager
{
private const string _stopCommand = "stop-commands" ;
2022-10-18 10:54:08 -04:00
private readonly Dictionary < string , IActionCommandExtension > _commandExtensions = new ( StringComparer . OrdinalIgnoreCase ) ;
private readonly HashSet < string > _registeredCommands = new ( StringComparer . OrdinalIgnoreCase ) ;
private readonly object _commandSerializeLock = new ( ) ;
2019-10-10 00:52:42 -04:00
private bool _stopProcessCommand = false ;
private string _stopToken = null ;
public override void Initialize ( IHostContext hostContext )
{
base . Initialize ( hostContext ) ;
_registeredCommands . Add ( _stopCommand ) ;
// Register all command extensions
var extensionManager = hostContext . GetService < IExtensionManager > ( ) ;
foreach ( var commandExt in extensionManager . GetExtensions < IActionCommandExtension > ( ) ? ? new List < IActionCommandExtension > ( ) )
{
Trace . Info ( $"Register action command extension for command {commandExt.Command}" ) ;
_commandExtensions [ commandExt . Command ] = commandExt ;
if ( commandExt . Command ! = "internal-set-repo-path" )
{
_registeredCommands . Add ( commandExt . Command ) ;
}
}
}
public void EnablePluginInternalCommand ( )
{
Trace . Info ( $"Enable plugin internal command extension." ) ;
_registeredCommands . Add ( "internal-set-repo-path" ) ;
}
public void DisablePluginInternalCommand ( )
{
Trace . Info ( $"Disable plugin internal command extension." ) ;
_registeredCommands . Remove ( "internal-set-repo-path" ) ;
}
2020-02-12 21:07:43 -05:00
public bool TryProcessCommand ( IExecutionContext context , string input , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
if ( string . IsNullOrEmpty ( input ) )
{
return false ;
}
// TryParse input to Command
ActionCommand actionCommand ;
if ( ! ActionCommand . TryParseV2 ( input , _registeredCommands , out actionCommand ) & &
! ActionCommand . TryParse ( input , _registeredCommands , out actionCommand ) )
{
return false ;
}
2021-07-13 11:38:16 -04:00
if ( ! ActionCommandManager . EnhancedAnnotationsEnabled ( context ) & & actionCommand . Command = = "notice" )
{
context . Debug ( $"Enhanced Annotations not enabled on the server: 'notice' command will not be processed." ) ;
return false ;
}
2021-06-07 10:58:00 -05:00
// Serialize order
2019-10-10 00:52:42 -04:00
lock ( _commandSerializeLock )
{
2021-06-07 10:58:00 -05:00
// Currently stopped
2019-10-10 00:52:42 -04:00
if ( _stopProcessCommand )
{
2021-06-07 10:58:00 -05:00
// Resume token
2019-10-10 00:52:42 -04:00
if ( ! string . IsNullOrEmpty ( _stopToken ) & &
string . Equals ( actionCommand . Command , _stopToken , StringComparison . OrdinalIgnoreCase ) )
{
context . Output ( input ) ;
context . Debug ( "Resume processing commands" ) ;
_registeredCommands . Remove ( _stopToken ) ;
_stopProcessCommand = false ;
_stopToken = null ;
return true ;
}
else
{
context . Debug ( $"Process commands has been stopped and waiting for '##[{_stopToken}]' to resume." ) ;
return false ;
}
}
2021-06-07 10:58:00 -05:00
// Currently processing
2019-10-10 00:52:42 -04:00
else
{
2021-06-07 10:58:00 -05:00
// Stop command
2019-10-10 00:52:42 -04:00
if ( string . Equals ( actionCommand . Command , _stopCommand , StringComparison . OrdinalIgnoreCase ) )
{
2021-09-29 20:44:01 +02:00
ValidateStopToken ( context , actionCommand . Data ) ;
2019-10-10 00:52:42 -04:00
_stopToken = actionCommand . Data ;
_stopProcessCommand = true ;
_registeredCommands . Add ( _stopToken ) ;
2021-09-29 20:44:01 +02:00
if ( _stopToken . Length > 6 )
{
HostContext . SecretMasker . AddValue ( _stopToken ) ;
}
context . Output ( input ) ;
context . Debug ( "Paused processing commands until the token you called ::stopCommands:: with is received" ) ;
2019-10-10 00:52:42 -04:00
return true ;
}
2021-06-07 10:58:00 -05:00
// Found command
2019-10-10 00:52:42 -04:00
else if ( _commandExtensions . TryGetValue ( actionCommand . Command , out IActionCommandExtension extension ) )
{
2019-11-04 14:31:21 -05:00
if ( context . EchoOnActionCommand & & ! extension . OmitEcho )
{
context . Output ( input ) ;
}
2019-10-25 10:38:56 -04:00
2019-10-10 00:52:42 -04:00
try
{
2020-02-12 21:07:43 -05:00
extension . ProcessCommand ( context , input , actionCommand , container ) ;
2019-10-10 00:52:42 -04:00
}
catch ( Exception ex )
{
2019-11-04 14:31:21 -05:00
var commandInformation = extension . OmitEcho ? extension . Command : input ;
context . Error ( $"Unable to process command '{commandInformation}' successfully." ) ;
2019-10-10 00:52:42 -04:00
context . Error ( ex ) ;
context . CommandResult = TaskResult . Failed ;
}
}
2021-06-07 10:58:00 -05:00
// Command not found
2019-10-10 00:52:42 -04:00
else
{
context . Warning ( $"Can't find command extension for ##[{actionCommand.Command}.command]." ) ;
}
}
}
return true ;
}
2021-07-13 11:38:16 -04:00
2021-09-29 20:44:01 +02:00
private void ValidateStopToken ( IExecutionContext context , string stopToken )
{
#if OS_WINDOWS
var envContext = context . ExpressionValues [ "env" ] as DictionaryContextData ;
#else
var envContext = context . ExpressionValues [ "env" ] as CaseSensitiveDictionaryContextData ;
#endif
var allowUnsecureStopCommandTokens = false ;
allowUnsecureStopCommandTokens = StringUtil . ConvertToBoolean ( Environment . GetEnvironmentVariable ( Constants . Variables . Actions . AllowUnsupportedStopCommandTokens ) ) ;
if ( ! allowUnsecureStopCommandTokens & & envContext . ContainsKey ( Constants . Variables . Actions . AllowUnsupportedStopCommandTokens ) )
{
allowUnsecureStopCommandTokens = StringUtil . ConvertToBoolean ( envContext [ Constants . Variables . Actions . AllowUnsupportedStopCommandTokens ] . ToString ( ) ) ;
}
bool isTokenInvalid = _registeredCommands . Contains ( stopToken )
| | string . IsNullOrEmpty ( stopToken )
| | string . Equals ( stopToken , "pause-logging" , StringComparison . OrdinalIgnoreCase ) ;
if ( isTokenInvalid )
{
var telemetry = new JobTelemetry
{
Message = $"Invoked ::stopCommand:: with token: [{stopToken}]" ,
Type = JobTelemetryType . ActionCommand
} ;
2022-02-11 16:18:41 -05:00
context . Global . JobTelemetry . Add ( telemetry ) ;
2021-09-29 20:44:01 +02:00
}
if ( isTokenInvalid & & ! allowUnsecureStopCommandTokens )
{
throw new Exception ( Constants . Runner . UnsupportedStopCommandTokenDisabled ) ;
}
}
2021-09-20 15:54:20 +02:00
internal static bool EnhancedAnnotationsEnabled ( IExecutionContext context )
{
2021-07-13 11:38:16 -04:00
return context . Global . Variables . GetBoolean ( "DistributedTask.EnhancedAnnotations" ) ? ? false ;
}
2019-10-10 00:52:42 -04:00
}
public interface IActionCommandExtension : IExtension
{
string Command { get ; }
2019-11-04 14:31:21 -05:00
bool OmitEcho { get ; }
2019-10-10 00:52:42 -04:00
2020-02-12 21:07:43 -05:00
void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container ) ;
2019-10-10 00:52:42 -04:00
}
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "internal-set-repo-path" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
if ( ! command . Properties . TryGetValue ( SetRepoPathCommandProperties . repoFullName , out string repoFullName ) | | string . IsNullOrEmpty ( repoFullName ) )
{
throw new Exception ( "Required field 'repoFullName' is missing in ##[internal-set-repo-path] command." ) ;
}
if ( ! command . Properties . TryGetValue ( SetRepoPathCommandProperties . workspaceRepo , out string workspaceRepo ) | | string . IsNullOrEmpty ( workspaceRepo ) )
{
throw new Exception ( "Required field 'workspaceRepo' is missing in ##[internal-set-repo-path] command." ) ;
}
var directoryManager = HostContext . GetService < IPipelineDirectoryManager > ( ) ;
var trackingConfig = directoryManager . UpdateRepositoryDirectory ( context , repoFullName , command . Data , StringUtil . ConvertToBoolean ( workspaceRepo ) ) ;
}
private static class SetRepoPathCommandProperties
{
public const String repoFullName = "repoFullName" ;
public const String workspaceRepo = "workspaceRepo" ;
}
}
public sealed class SetEnvCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "set-env" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
2020-10-02 11:34:37 -04:00
var allowUnsecureCommands = false ;
bool . TryParse ( Environment . GetEnvironmentVariable ( Constants . Variables . Actions . AllowUnsupportedCommands ) , out allowUnsecureCommands ) ;
// Apply environment from env context, env context contains job level env and action's env block
#if OS_WINDOWS
var envContext = context . ExpressionValues [ "env" ] as DictionaryContextData ;
#else
var envContext = context . ExpressionValues [ "env" ] as CaseSensitiveDictionaryContextData ;
#endif
if ( ! allowUnsecureCommands & & envContext . ContainsKey ( Constants . Variables . Actions . AllowUnsupportedCommands ) )
{
bool . TryParse ( envContext [ Constants . Variables . Actions . AllowUnsupportedCommands ] . ToString ( ) , out allowUnsecureCommands ) ;
}
2020-11-16 08:20:43 -05:00
if ( ! allowUnsecureCommands )
2020-10-02 11:34:37 -04:00
{
throw new Exception ( String . Format ( Constants . Runner . UnsupportedCommandMessageDisabled , this . Command ) ) ;
}
2019-10-10 00:52:42 -04:00
if ( ! command . Properties . TryGetValue ( SetEnvCommandProperties . Name , out string envName ) | | string . IsNullOrEmpty ( envName ) )
{
throw new Exception ( "Required field 'name' is missing in ##[set-env] command." ) ;
}
2020-11-02 14:09:29 -05:00
foreach ( var blocked in _setEnvBlockList )
{
if ( string . Equals ( blocked , envName , StringComparison . OrdinalIgnoreCase ) )
{
// Log Telemetry and let user know they shouldn't do this
var issue = new Issue ( )
{
Type = IssueType . Error ,
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
} ;
issue . Data [ Constants . Runner . InternalTelemetryIssueDataKey ] = $"{Constants.Runner.UnsupportedCommand}_{envName}" ;
2023-05-10 16:24:02 +02:00
context . AddIssue ( issue , ExecutionContextLogOptions . Default ) ;
2020-11-02 14:09:29 -05:00
return ;
}
}
2020-07-19 19:05:47 -04:00
context . Global . EnvironmentVariables [ envName ] = command . Data ;
2019-10-10 00:52:42 -04:00
context . SetEnvContext ( envName , command . Data ) ;
context . Debug ( $"{envName}='{command.Data}'" ) ;
}
private static class SetEnvCommandProperties
{
public const String Name = "name" ;
}
2020-11-02 14:09:29 -05:00
2021-09-20 15:54:20 +02:00
private string [ ] _setEnvBlockList =
2020-11-02 14:09:29 -05:00
{
"NODE_OPTIONS"
} ;
2019-10-10 00:52:42 -04:00
}
public sealed class SetOutputCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "set-output" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
2022-10-04 12:14:22 +01:00
if ( context . Global . Variables . GetBoolean ( "DistributedTask.DeprecateStepOutputCommands" ) ? ? false )
{
var issue = new Issue ( )
{
Type = IssueType . Warning ,
Message = String . Format ( Constants . Runner . UnsupportedCommandMessage , this . Command )
} ;
issue . Data [ Constants . Runner . InternalTelemetryIssueDataKey ] = Constants . Runner . UnsupportedCommand ;
2023-05-10 16:24:02 +02:00
context . AddIssue ( issue , ExecutionContextLogOptions . Default ) ;
2022-10-04 12:14:22 +01:00
}
2026-02-10 12:28:42 -06:00
if ( ! context . Global . HasDeprecatedSetOutput )
{
context . Global . HasDeprecatedSetOutput = true ;
var telemetry = new JobTelemetry
{
Type = JobTelemetryType . ActionCommand ,
Message = "DeprecatedCommand: set-output"
} ;
context . Global . JobTelemetry . Add ( telemetry ) ;
}
2019-10-10 00:52:42 -04:00
if ( ! command . Properties . TryGetValue ( SetOutputCommandProperties . Name , out string outputName ) | | string . IsNullOrEmpty ( outputName ) )
{
throw new Exception ( "Required field 'name' is missing in ##[set-output] command." ) ;
}
context . SetOutput ( outputName , command . Data , out var reference ) ;
context . Debug ( $"{reference}='{command.Data}'" ) ;
}
private static class SetOutputCommandProperties
{
public const String Name = "name" ;
}
}
public sealed class SaveStateCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "save-state" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
2022-10-04 12:14:22 +01:00
if ( context . Global . Variables . GetBoolean ( "DistributedTask.DeprecateStepOutputCommands" ) ? ? false )
{
var issue = new Issue ( )
{
Type = IssueType . Warning ,
Message = String . Format ( Constants . Runner . UnsupportedCommandMessage , this . Command )
} ;
issue . Data [ Constants . Runner . InternalTelemetryIssueDataKey ] = Constants . Runner . UnsupportedCommand ;
2023-05-10 16:24:02 +02:00
context . AddIssue ( issue , ExecutionContextLogOptions . Default ) ;
2022-10-04 12:14:22 +01:00
}
2026-02-10 12:28:42 -06:00
if ( ! context . Global . HasDeprecatedSaveState )
{
context . Global . HasDeprecatedSaveState = true ;
var telemetry = new JobTelemetry
{
Type = JobTelemetryType . ActionCommand ,
Message = "DeprecatedCommand: save-state"
} ;
context . Global . JobTelemetry . Add ( telemetry ) ;
}
2019-10-10 00:52:42 -04:00
if ( ! command . Properties . TryGetValue ( SaveStateCommandProperties . Name , out string stateName ) | | string . IsNullOrEmpty ( stateName ) )
{
throw new Exception ( "Required field 'name' is missing in ##[save-state] command." ) ;
}
2021-07-28 15:35:21 -04:00
// Embedded steps (composite) keep track of the state at the root level
if ( context . IsEmbedded )
{
var id = context . EmbeddedId ;
if ( ! context . Root . EmbeddedIntraActionState . ContainsKey ( id ) )
{
context . Root . EmbeddedIntraActionState [ id ] = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
}
context . Root . EmbeddedIntraActionState [ id ] [ stateName ] = command . Data ;
}
// Otherwise modify the ExecutionContext
else
{
context . IntraActionState [ stateName ] = command . Data ;
}
2019-10-10 00:52:42 -04:00
context . Debug ( $"Save intra-action state {stateName} = {command.Data}" ) ;
}
private static class SaveStateCommandProperties
{
public const String Name = "name" ;
}
}
public sealed class AddMaskCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "add-mask" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > true ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
if ( string . IsNullOrWhiteSpace ( command . Data ) )
{
2019-10-25 10:38:56 -04:00
context . Warning ( "Can't add secret mask for empty string in ##[add-mask] command." ) ;
2019-10-10 00:52:42 -04:00
}
else
{
2019-11-04 14:31:21 -05:00
if ( context . EchoOnActionCommand )
{
context . Output ( $"::{Command}::***" ) ;
}
2019-10-10 00:52:42 -04:00
HostContext . SecretMasker . AddValue ( command . Data ) ;
Trace . Info ( $"Add new secret mask with length of {command.Data.Length}" ) ;
2021-12-01 09:53:13 -05:00
// Also add each individual line. Typically individual lines are processed from STDOUT of child processes.
var split = command . Data . Split ( new [ ] { '\r' , '\n' } , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
foreach ( var item in split )
{
HostContext . SecretMasker . AddValue ( item ) ;
}
2019-10-10 00:52:42 -04:00
}
}
}
public sealed class AddPathCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "add-path" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2021-09-20 15:54:20 +02:00
{
2020-10-02 11:34:37 -04:00
var allowUnsecureCommands = false ;
bool . TryParse ( Environment . GetEnvironmentVariable ( Constants . Variables . Actions . AllowUnsupportedCommands ) , out allowUnsecureCommands ) ;
// Apply environment from env context, env context contains job level env and action's env block
#if OS_WINDOWS
var envContext = context . ExpressionValues [ "env" ] as DictionaryContextData ;
#else
var envContext = context . ExpressionValues [ "env" ] as CaseSensitiveDictionaryContextData ;
#endif
if ( ! allowUnsecureCommands & & envContext . ContainsKey ( Constants . Variables . Actions . AllowUnsupportedCommands ) )
{
bool . TryParse ( envContext [ Constants . Variables . Actions . AllowUnsupportedCommands ] . ToString ( ) , out allowUnsecureCommands ) ;
}
2020-11-16 08:20:43 -05:00
if ( ! allowUnsecureCommands )
2020-10-02 11:34:37 -04:00
{
throw new Exception ( String . Format ( Constants . Runner . UnsupportedCommandMessageDisabled , this . Command ) ) ;
}
2019-10-10 00:52:42 -04:00
ArgUtil . NotNullOrEmpty ( command . Data , "path" ) ;
2020-07-19 19:05:47 -04:00
context . Global . PrependPath . RemoveAll ( x = > string . Equals ( x , command . Data , StringComparison . CurrentCulture ) ) ;
context . Global . PrependPath . Add ( command . Data ) ;
2019-10-10 00:52:42 -04:00
}
}
public sealed class AddMatcherCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "add-matcher" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
var file = command . Data ;
// File is required
if ( string . IsNullOrEmpty ( file ) )
{
context . Warning ( "File path must be specified." ) ;
return ;
}
// Translate file path back from container path
2020-02-12 21:07:43 -05:00
if ( container ! = null )
2019-10-10 00:52:42 -04:00
{
2020-02-12 21:07:43 -05:00
file = container . TranslateToHostPath ( file ) ;
2019-10-10 00:52:42 -04:00
}
// Root the path
if ( ! Path . IsPathRooted ( file ) )
{
var githubContext = context . ExpressionValues [ "github" ] as GitHubContext ;
ArgUtil . NotNull ( githubContext , nameof ( githubContext ) ) ;
var workspace = githubContext [ "workspace" ] . ToString ( ) ;
ArgUtil . NotNullOrEmpty ( workspace , "workspace" ) ;
file = Path . Combine ( workspace , file ) ;
}
// Load the config
var config = IOUtil . LoadObject < IssueMatchersConfig > ( file ) ;
// Add
if ( config ? . Matchers ? . Count > 0 )
{
config . Validate ( ) ;
context . AddMatchers ( config ) ;
}
}
}
public sealed class RemoveMatcherCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "remove-matcher" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
command . Properties . TryGetValue ( RemoveMatcherCommandProperties . Owner , out string owner ) ;
var file = command . Data ;
// Owner and file are mutually exclusive
if ( ! string . IsNullOrEmpty ( owner ) & & ! string . IsNullOrEmpty ( file ) )
{
2019-10-25 10:38:56 -04:00
context . Warning ( "Either specify an owner name or a file path in ##[remove-matcher] command. Both values cannot be set." ) ;
2019-10-10 00:52:42 -04:00
return ;
}
// Owner or file is required
if ( string . IsNullOrEmpty ( owner ) & & string . IsNullOrEmpty ( file ) )
{
2019-10-25 10:38:56 -04:00
context . Warning ( "Either an owner name or a file path must be specified in ##[remove-matcher] command." ) ;
2019-10-10 00:52:42 -04:00
return ;
}
// Remove by owner
if ( ! string . IsNullOrEmpty ( owner ) )
{
context . RemoveMatchers ( new [ ] { owner } ) ;
}
// Remove by file
else
{
// Translate file path back from container path
2020-02-12 21:07:43 -05:00
if ( container ! = null )
2019-10-10 00:52:42 -04:00
{
2020-02-12 21:07:43 -05:00
file = container . TranslateToHostPath ( file ) ;
2019-10-10 00:52:42 -04:00
}
// Root the path
if ( ! Path . IsPathRooted ( file ) )
{
var githubContext = context . ExpressionValues [ "github" ] as GitHubContext ;
ArgUtil . NotNull ( githubContext , nameof ( githubContext ) ) ;
var workspace = githubContext [ "workspace" ] . ToString ( ) ;
ArgUtil . NotNullOrEmpty ( workspace , "workspace" ) ;
file = Path . Combine ( workspace , file ) ;
}
// Load the config
var config = IOUtil . LoadObject < IssueMatchersConfig > ( file ) ;
if ( config ? . Matchers ? . Count > 0 )
{
// Remove
context . RemoveMatchers ( config . Matchers . Select ( x = > x . Owner ) ) ;
}
}
}
private static class RemoveMatcherCommandProperties
{
public const string Owner = "owner" ;
}
}
public sealed class DebugCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "debug" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > true ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string inputLine , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
context . Debug ( command . Data ) ;
}
}
public sealed class WarningCommandExtension : IssueCommandExtension
{
public override IssueType Type = > IssueType . Warning ;
public override string Command = > "warning" ;
}
public sealed class ErrorCommandExtension : IssueCommandExtension
{
public override IssueType Type = > IssueType . Error ;
public override string Command = > "error" ;
}
2021-07-13 11:38:16 -04:00
public sealed class NoticeCommandExtension : IssueCommandExtension
{
public override IssueType Type = > IssueType . Notice ;
public override string Command = > "notice" ;
}
2019-10-10 00:52:42 -04:00
public abstract class IssueCommandExtension : RunnerService , IActionCommandExtension
{
public abstract IssueType Type { get ; }
public abstract string Command { get ; }
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > true ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string inputLine , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
2022-08-24 16:02:51 -04:00
ValidateLinesAndColumns ( command , context ) ;
2022-10-04 12:14:22 +01:00
2019-10-28 15:45:27 -04:00
command . Properties . TryGetValue ( IssueCommandProperties . File , out string file ) ;
command . Properties . TryGetValue ( IssueCommandProperties . Line , out string line ) ;
command . Properties . TryGetValue ( IssueCommandProperties . Column , out string column ) ;
2021-09-20 15:54:20 +02:00
if ( ! ActionCommandManager . EnhancedAnnotationsEnabled ( context ) )
2021-07-13 11:38:16 -04:00
{
context . Debug ( "Enhanced Annotations not enabled on the server. The 'title', 'end_line', and 'end_column' fields are unsupported." ) ;
}
2021-09-20 15:54:20 +02:00
2022-10-18 10:54:08 -04:00
Issue issue = new ( )
2019-10-10 00:52:42 -04:00
{
Category = "General" ,
Type = this . Type ,
Message = command . Data
} ;
2019-10-28 15:45:27 -04:00
if ( ! string . IsNullOrEmpty ( file ) )
{
issue . Category = "Code" ;
2020-02-12 21:07:43 -05:00
if ( container ! = null )
2019-10-28 15:45:27 -04:00
{
// Translate file path back from container path
2020-02-12 21:07:43 -05:00
file = container . TranslateToHostPath ( file ) ;
2019-10-28 15:45:27 -04:00
command . Properties [ IssueCommandProperties . File ] = file ;
}
// Get the values that represent the server path given a local path
string repoName = context . GetGitHubContext ( "repository" ) ;
var repoPath = context . GetGitHubContext ( "workspace" ) ;
string relativeSourcePath = IOUtil . MakeRelative ( file , repoPath ) ;
if ( ! string . Equals ( relativeSourcePath , file , IOUtil . FilePathStringComparison ) )
{
// add repo info
if ( ! string . IsNullOrEmpty ( repoName ) )
{
command . Properties [ "repo" ] = repoName ;
}
if ( ! string . IsNullOrEmpty ( relativeSourcePath ) )
{
// replace sourcePath with the new relative path
// prefer `/` on all platforms
command . Properties [ IssueCommandProperties . File ] = relativeSourcePath . Replace ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
}
}
}
foreach ( var property in command . Properties )
{
2020-04-27 23:44:17 -04:00
if ( ! string . Equals ( property . Key , Constants . Runner . InternalTelemetryIssueDataKey , StringComparison . OrdinalIgnoreCase ) )
{
issue . Data [ property . Key ] = property . Value ;
}
2019-10-28 15:45:27 -04:00
}
2023-05-10 16:24:02 +02:00
context . AddIssue ( issue , ExecutionContextLogOptions . Default ) ;
2019-10-10 00:52:42 -04:00
}
2019-10-28 15:45:27 -04:00
2021-09-20 15:54:20 +02:00
public static void ValidateLinesAndColumns ( ActionCommand command , IExecutionContext context )
2021-07-13 11:38:16 -04:00
{
command . Properties . TryGetValue ( IssueCommandProperties . Line , out string line ) ;
command . Properties . TryGetValue ( IssueCommandProperties . EndLine , out string endLine ) ;
command . Properties . TryGetValue ( IssueCommandProperties . Column , out string column ) ;
command . Properties . TryGetValue ( IssueCommandProperties . EndColumn , out string endColumn ) ;
var hasStartLine = int . TryParse ( line , out int lineNumber ) ;
var hasEndLine = int . TryParse ( endLine , out int endLineNumber ) ;
var hasStartColumn = int . TryParse ( column , out int columnNumber ) ;
var hasEndColumn = int . TryParse ( endColumn , out int endColumnNumber ) ;
var hasColumn = hasStartColumn | | hasEndColumn ;
if ( hasEndLine & & ! hasStartLine )
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.EndLine}' can only be set if '{IssueCommandProperties.Line}' is provided" ) ;
command . Properties [ IssueCommandProperties . Line ] = endLine ;
hasStartLine = true ;
line = endLine ;
}
if ( hasEndColumn & & ! hasStartColumn )
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.EndColumn}' can only be set if '{IssueCommandProperties.Column}' is provided" ) ;
command . Properties [ IssueCommandProperties . Column ] = endColumn ;
hasStartColumn = true ;
column = endColumn ;
}
2021-09-20 15:54:20 +02:00
if ( ! hasStartLine & & hasColumn )
2021-07-13 11:38:16 -04:00
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.Column}' and '{IssueCommandProperties.EndColumn}' can only be set if '{IssueCommandProperties.Line}' value is provided." ) ;
command . Properties . Remove ( IssueCommandProperties . Column ) ;
command . Properties . Remove ( IssueCommandProperties . EndColumn ) ;
}
2021-09-20 15:54:20 +02:00
if ( hasEndLine & & line ! = endLine & & hasColumn )
2021-07-13 11:38:16 -04:00
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.Column}' and '{IssueCommandProperties.EndColumn}' cannot be set if '{IssueCommandProperties.Line}' and '{IssueCommandProperties.EndLine}' are different values." ) ;
command . Properties . Remove ( IssueCommandProperties . Column ) ;
command . Properties . Remove ( IssueCommandProperties . EndColumn ) ;
}
2021-09-20 15:54:20 +02:00
if ( hasStartLine & & hasEndLine & & endLineNumber < lineNumber )
2021-07-13 11:38:16 -04:00
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.EndLine}' cannot be less than '{IssueCommandProperties.Line}'." ) ;
command . Properties . Remove ( IssueCommandProperties . Line ) ;
command . Properties . Remove ( IssueCommandProperties . EndLine ) ;
}
2021-09-20 15:54:20 +02:00
if ( hasStartColumn & & hasEndColumn & & endColumnNumber < columnNumber )
2021-07-13 11:38:16 -04:00
{
context . Debug ( $"Invalid {command.Command} command value. '{IssueCommandProperties.EndColumn}' cannot be less than '{IssueCommandProperties.Column}'." ) ;
command . Properties . Remove ( IssueCommandProperties . Column ) ;
command . Properties . Remove ( IssueCommandProperties . EndColumn ) ;
}
}
2019-10-28 15:45:27 -04:00
private static class IssueCommandProperties
{
public const String File = "file" ;
public const String Line = "line" ;
2021-07-13 11:38:16 -04:00
public const String EndLine = "endLine" ;
2019-10-28 15:45:27 -04:00
public const String Column = "col" ;
2021-07-13 11:38:16 -04:00
public const String EndColumn = "endColumn" ;
public const String Title = "title" ;
2019-10-28 15:45:27 -04:00
}
2019-10-10 00:52:42 -04:00
}
public sealed class GroupCommandExtension : GroupingCommandExtension
{
public override string Command = > "group" ;
}
public sealed class EndGroupCommandExtension : GroupingCommandExtension
{
public override string Command = > "endgroup" ;
}
public abstract class GroupingCommandExtension : RunnerService , IActionCommandExtension
{
public abstract string Command { get ; }
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-10 00:52:42 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-10 00:52:42 -04:00
{
var data = this is GroupCommandExtension ? command . Data : string . Empty ;
context . Output ( $"##[{Command}]{data}" ) ;
2019-10-25 10:38:56 -04:00
}
}
public sealed class EchoCommandExtension : RunnerService , IActionCommandExtension
{
public string Command = > "echo" ;
2019-11-04 14:31:21 -05:00
public bool OmitEcho = > false ;
2019-10-25 10:38:56 -04:00
public Type ExtensionType = > typeof ( IActionCommandExtension ) ;
2020-02-12 21:07:43 -05:00
public void ProcessCommand ( IExecutionContext context , string line , ActionCommand command , ContainerInfo container )
2019-10-25 10:38:56 -04:00
{
ArgUtil . NotNullOrEmpty ( command . Data , "value" ) ;
switch ( command . Data . Trim ( ) . ToUpperInvariant ( ) )
{
case "ON" :
context . EchoOnActionCommand = true ;
context . Debug ( "Setting echo command value to 'on'" ) ;
break ;
case "OFF" :
context . EchoOnActionCommand = false ;
context . Debug ( "Setting echo command value to 'off'" ) ;
break ;
default :
throw new Exception ( $"Invalid echo command value. Possible values can be: 'on', 'off'. Current value is: '{command.Data}'." ) ;
}
2019-10-10 00:52:42 -04:00
}
}
}