Skip to content

Commit

Permalink
Implement new echo behavior and command (actions#139)
Browse files Browse the repository at this point in the history
* Remove controlling echoing by command

* Add 'echo on' and 'echo off' action commands

* PR feedback and add L0 tests

* Register new command

* Eric's PR feedback

* Tweak logging a bit

* Rename EchoOnActionCommandSuccess -> EchoOnActionCommand

* More PR reaction

* Make warning messages in Action Commands not rely on context from echo commands
  • Loading branch information
juliobbv authored and thboop committed Oct 25, 2019
1 parent afd233b commit 82e9857
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 44 deletions.
1 change: 1 addition & 0 deletions src/Runner.Common/ExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private List<IExtension> LoadExtensions<T>() where T : class, IExtension
Add<T>(extensions, "GitHub.Runner.Worker.DebugCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.GroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EchoCommandExtension, Runner.Worker");
break;
default:
// This should never happen.
Expand Down
104 changes: 62 additions & 42 deletions src/Runner.Worker/ActionCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,26 +107,35 @@ public bool TryProcessCommand(IExecutionContext context, string input)
}
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
{
bool omitEcho;
bool commandHasBeenOutput = false;

try
{
extension.ProcessCommand(context, input, actionCommand, out omitEcho);
if (context.EchoOnActionCommand)
{
context.Output(input);
context.Debug($"Processing command '{actionCommand.Command}'");
commandHasBeenOutput = true;
}

extension.ProcessCommand(context, input, actionCommand);

if (context.EchoOnActionCommand)
{
context.Debug($"Processed command '{actionCommand.Command}' successfully");
}
}
catch (Exception ex)
{
omitEcho = true;
context.Output(input);
if (!commandHasBeenOutput)
{
context.Output(input);
}

context.Error($"Unable to process command '{input}' successfully.");
context.Error(ex);
context.CommandResult = TaskResult.Failed;
}

if (!omitEcho)
{
context.Output(input);
context.Debug($"Processed command");
}

}
else
{
Expand All @@ -143,7 +152,7 @@ public interface IActionCommandExtension : IExtension
{
string Command { get; }

void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho);
void ProcessCommand(IExecutionContext context, string line, ActionCommand command);
}

public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
Expand All @@ -152,7 +161,7 @@ public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, I

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetRepoPathCommandProperties.repoFullName, out string repoFullName) || string.IsNullOrEmpty(repoFullName))
{
Expand All @@ -166,8 +175,6 @@ public void ProcessCommand(IExecutionContext context, string line, ActionCommand

var directoryManager = HostContext.GetService<IPipelineDirectoryManager>();
var trackingConfig = directoryManager.UpdateRepositoryDirectory(context, repoFullName, command.Data, StringUtil.ConvertToBoolean(workspaceRepo));

omitEcho = true;
}

private static class SetRepoPathCommandProperties
Expand All @@ -183,7 +190,7 @@ public sealed class SetEnvCommandExtension : RunnerService, IActionCommandExtens

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
{
Expand All @@ -192,9 +199,7 @@ public void ProcessCommand(IExecutionContext context, string line, ActionCommand

context.EnvironmentVariables[envName] = command.Data;
context.SetEnvContext(envName, command.Data);
context.Output(line);
context.Debug($"{envName}='{command.Data}'");
omitEcho = true;
}

private static class SetEnvCommandProperties
Expand All @@ -209,17 +214,15 @@ public sealed class SetOutputCommandExtension : RunnerService, IActionCommandExt

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
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.Output(line);
context.Debug($"{reference}='{command.Data}'");
omitEcho = true;
}

private static class SetOutputCommandProperties
Expand All @@ -234,7 +237,7 @@ public sealed class SaveStateCommandExtension : RunnerService, IActionCommandExt

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
{
Expand All @@ -243,7 +246,6 @@ public void ProcessCommand(IExecutionContext context, string line, ActionCommand

context.IntraActionState[stateName] = command.Data;
context.Debug($"Save intra-action state {stateName} = {command.Data}");
omitEcho = true;
}

private static class SaveStateCommandProperties
Expand All @@ -258,19 +260,17 @@ public sealed class AddMaskCommandExtension : RunnerService, IActionCommandExten

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (string.IsNullOrWhiteSpace(command.Data))
{
context.Warning("Can't add secret mask for empty string.");
context.Warning("Can't add secret mask for empty string in ##[add-mask] command.");
}
else
{
HostContext.SecretMasker.AddValue(command.Data);
Trace.Info($"Add new secret mask with length of {command.Data.Length}");
}

omitEcho = true;
}
}

Expand All @@ -280,12 +280,11 @@ public sealed class AddPathCommandExtension : RunnerService, IActionCommandExten

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
ArgUtil.NotNullOrEmpty(command.Data, "path");
context.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
context.PrependPath.Add(command.Data);
omitEcho = false;
}
}

Expand All @@ -295,9 +294,8 @@ public sealed class AddMatcherCommandExtension : RunnerService, IActionCommandEx

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
var file = command.Data;

// File is required
Expand Down Expand Up @@ -342,23 +340,22 @@ public sealed class RemoveMatcherCommandExtension : RunnerService, IActionComman

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
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))
{
context.Warning("Either specify a matcher owner name or a file path. Both values cannot be set.");
context.Warning("Either specify an owner name or a file path in ##[remove-matcher] command. Both values cannot be set.");
return;
}

// Owner or file is required
if (string.IsNullOrEmpty(owner) && string.IsNullOrEmpty(file))
{
context.Warning("Either a matcher owner name or a file path must be specified.");
context.Warning("Either an owner name or a file path must be specified in ##[remove-matcher] command.");
return;
}

Expand Down Expand Up @@ -410,9 +407,8 @@ public sealed class DebugCommandExtension : RunnerService, IActionCommandExtensi

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;
context.Debug(command.Data);
}
}
Expand All @@ -438,10 +434,8 @@ public abstract class IssueCommandExtension : RunnerService, IActionCommandExten

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;

Issue issue = new Issue()
{
Category = "General",
Expand All @@ -468,11 +462,37 @@ public abstract class GroupingCommandExtension : RunnerService, IActionCommandEx
public abstract string Command { get; }
public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
var data = this is GroupCommandExtension ? command.Data : string.Empty;
context.Output($"##[{Command}]{data}");
omitEcho = true;
}
}

public sealed class EchoCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "echo";

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
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}'.");
break;
}
}
}
}
12 changes: 10 additions & 2 deletions src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Newtonsoft.Json;
using System.Text;
using System.Collections;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;

namespace GitHub.Runner.Worker
{
Expand Down Expand Up @@ -62,6 +62,8 @@ public interface IExecutionContext : IRunnerService
// Only job level ExecutionContext has PostJobSteps
Stack<IStep> PostJobSteps { get; }

bool EchoOnActionCommand { get; set; }

// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken();
Expand Down Expand Up @@ -153,6 +155,8 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext
// Only job level ExecutionContext has PostJobSteps
public Stack<IStep> PostJobSteps { get; private set; }

public bool EchoOnActionCommand { get; set; }


public TaskResult? Result
{
Expand Down Expand Up @@ -292,6 +296,7 @@ public IExecutionContext CreateChild(Guid recordId, string displayName, string r
child.PrependPath = PrependPath;
child.Container = Container;
child.ServiceContainers = ServiceContainers;
child.EchoOnActionCommand = EchoOnActionCommand;

if (recordOrder != null)
{
Expand Down Expand Up @@ -704,6 +709,9 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation
_logger = HostContext.CreateService<IPagingLogger>();
_logger.Setup(_mainTimelineId, _record.Id);

// Initialize 'echo on action command success' property, default to false, unless Step_Debug is set
EchoOnActionCommand = Variables.Step_Debug ?? false;

// Verbosity (from GitHub.Step_Debug).
WriteDebug = Variables.Step_Debug ?? false;

Expand Down
Loading

0 comments on commit 82e9857

Please sign in to comment.