Skip to content

Commit

Permalink
script runner
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsciple committed May 10, 2019
1 parent 1ba3114 commit 20edd46
Show file tree
Hide file tree
Showing 18 changed files with 375 additions and 201 deletions.
2 changes: 1 addition & 1 deletion src/Agent.Listener/Agent.Listener.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
<PackageReference Include="vss-api-netcore" Version="0.5.106-private" />
<PackageReference Include="vss-api-netcore" Version="0.5.113-private" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
2 changes: 1 addition & 1 deletion src/Agent.PluginHost/Agent.PluginHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="vss-api-netcore" Version="0.5.106-private" />
<PackageReference Include="vss-api-netcore" Version="0.5.113-private" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
2 changes: 1 addition & 1 deletion src/Agent.Plugins/Agent.Plugins.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="vss-api-netcore" Version="0.5.106-private" />
<PackageReference Include="vss-api-netcore" Version="0.5.113-private" />
<PackageReference Include="azuredevops-testresultparser" Version="1.0.1" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/Agent.Sdk/Agent.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="vss-api-netcore" Version="0.5.106-private" />
<PackageReference Include="vss-api-netcore" Version="0.5.113-private" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
</ItemGroup>
Expand Down
41 changes: 28 additions & 13 deletions src/Agent.Worker/ActionCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public bool TryProcessCommand(IExecutionContext context, string input)
{
if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase))
{
context.Output($"Stop process further commands till '##[{actionCommand.Data}]' come in.");
context.Output(input);
context.Output($"{WellKnownTags.Debug}Paused processing commands until '##[{actionCommand.Data}]' is received");
_stopToken = actionCommand.Data;
_stopProcessCommand = true;
_registeredCommands.Add(_stopToken);
Expand All @@ -72,27 +73,33 @@ public bool TryProcessCommand(IExecutionContext context, string input)
else if (!string.IsNullOrEmpty(_stopToken) &&
string.Equals(actionCommand.Command, _stopToken, StringComparison.OrdinalIgnoreCase))
{
context.Output($"Resume process further commands.");
context.Output(input);
context.Output($"{WellKnownTags.Debug}Resume processing commands");
_registeredCommands.Remove(_stopToken);
_stopProcessCommand = false;
_stopToken = null;
return true;
}
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
{
bool omitEcho;
try
{
extension.ProcessCommand(context, actionCommand);
extension.ProcessCommand(context, input, actionCommand, out omitEcho);
}
catch (Exception ex)
{
omitEcho = true;
context.Output(input);
context.Error(StringUtil.Loc("CommandProcessFailed", input));
context.Error(ex);
context.CommandResult = TaskResult.Failed;
}
finally

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

}
Expand All @@ -111,7 +118,7 @@ public interface IActionCommandExtension : IExtension
{
string Command { get; }

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

public sealed class SetEnvCommandExtension : AgentService, IActionCommandExtension
Expand All @@ -120,14 +127,17 @@ public sealed class SetEnvCommandExtension : AgentService, IActionCommandExtensi

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, ActionCommand command)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
{
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
{
throw new Exception(StringUtil.Loc("MissingEnvName"));
}

context.SetVariable(envName, command.Data);
context.Output(line);
context.Output($"{WellKnownTags.Debug}{envName}='{command.Data}'");
omitEcho = true;
}

private static class SetEnvCommandProperties
Expand All @@ -142,14 +152,17 @@ public sealed class SetOutputCommandExtension : AgentService, IActionCommandExte

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, ActionCommand command)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
{
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
{
throw new Exception(StringUtil.Loc("MissingOutputName"));
}

context.SetOutput(outputName, command.Data);
context.SetOutput(outputName, command.Data, out var reference);
context.Output(line);
context.Output($"{WellKnownTags.Debug}{reference}='{command.Data}'");
omitEcho = true;
}

private static class SetOutputCommandProperties
Expand All @@ -164,14 +177,14 @@ public sealed class SetSecretCommandExtension : AgentService, IActionCommandExte

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, ActionCommand command)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
{
if (!command.Properties.TryGetValue(SetSecretCommandProperties.Name, out string secretName) || string.IsNullOrEmpty(secretName))
{
throw new Exception(StringUtil.Loc("MissingSecretName"));
}

context.SetOutput(secretName, command.Data, isSecret: true);
throw new NotSupportedException("Not supported yet");
}

private static class SetSecretCommandProperties
Expand All @@ -186,11 +199,12 @@ public sealed class AddPathCommandExtension : AgentService, IActionCommandExtens

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, ActionCommand command)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
{
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 @@ -215,8 +229,9 @@ public abstract class IssueCommandExtension : AgentService, IActionCommandExtens

public Type ExtensionType => typeof(IActionCommandExtension);

public void ProcessCommand(IExecutionContext context, ActionCommand command)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
{
omitEcho = false;
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
Expand Down
77 changes: 77 additions & 0 deletions src/Agent.Worker/ActionsContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Microsoft.TeamFoundation.DistributedTask.WebApi;
using Microsoft.VisualStudio.Services.Agent.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Microsoft.VisualStudio.Services.Agent.Worker
{
public sealed class ActionsContext : IReadOnlyDictionary<string, object>
{
private static readonly Regex _propertyRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled);
private readonly Dictionary<String, Object> _dictionary = new Dictionary<String, Object>(StringComparer.OrdinalIgnoreCase);

public Int32 Count => _dictionary.Count;

public IEnumerable<string> Keys => _dictionary.Keys;

public IEnumerable<object> Values => _dictionary.Values;

public object this[string key] => _dictionary[key];

public Boolean ContainsKey(string key) => _dictionary.ContainsKey(key);

public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _dictionary.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();

public Boolean TryGetValue(
string key,
out object value)
{
return _dictionary.TryGetValue(key, out value);
}

public void SetOutput(
string stepName,
string key,
string value,
out string reference)
{
var action = GetAction(stepName);
var outputs = action["outputs"] as Dictionary<String, String>;
outputs[key] = value;
if (_propertyRegex.IsMatch(key))
{
reference = $"actions.{stepName}.outputs.{key}";
}
else
{
reference = $"actions['{stepName}']['outputs']['{key}']";
}
}

public void SetResult(
string stepName,
string result)
{
var action = GetAction(stepName);
action["result"] = result;
}

private Dictionary<String, Object> GetAction(string stepName)
{
if (_dictionary.TryGetValue(stepName, out var actionObject))
{
return actionObject as Dictionary<String, Object>;
}

var action = new Dictionary<String, Object>(StringComparer.OrdinalIgnoreCase);
action.Add("result", null);
action.Add("outputs", new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase));
_dictionary.Add(stepName, action);
return action;
}
}
}
2 changes: 1 addition & 1 deletion src/Agent.Worker/Agent.Worker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="vss-api-netcore" Version="0.5.106-private" />
<PackageReference Include="vss-api-netcore" Version="0.5.113-private" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
</ItemGroup>
Expand Down
69 changes: 52 additions & 17 deletions src/Agent.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
Expand All @@ -10,6 +11,7 @@
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.TeamFoundation.DistributedTask.WebApi;
using Pipelines = Microsoft.TeamFoundation.DistributedTask.Pipelines;
using ObjectTemplating = Microsoft.TeamFoundation.DistributedTask.ObjectTemplating;
using Microsoft.VisualStudio.Services.Agent.Util;

namespace Microsoft.VisualStudio.Services.Agent.Worker
Expand Down Expand Up @@ -38,6 +40,7 @@ public interface IExecutionContext : IAgentService
Variables Variables { get; }
Variables TaskVariables { get; }
HashSet<string> OutputVariables { get; }
IDictionary<String, Object> ExpressionValues { get; }
List<IAsyncCommandContext> AsyncCommands { get; }
List<string> PrependPath { get; }
ContainerInfo Container { get; }
Expand All @@ -57,7 +60,7 @@ public interface IExecutionContext : IAgentService
void Start(string currentOperation = null);
TaskResult Complete(TaskResult? result = null, string currentOperation = null, string resultCode = null);
void SetVariable(string name, string value, bool isSecret = false, bool isOutput = false, bool isFilePath = false);
void SetOutput(string name, string value, bool isSecret = false);
void SetOutput(string name, string value, out string reference);
void SetTimeout(TimeSpan? timeout);
void AddIssue(Issue issue);
void Progress(int percentage, string currentOperation = null);
Expand Down Expand Up @@ -103,6 +106,7 @@ public sealed class ExecutionContext : AgentService, IExecutionContext
public Variables Variables { get; private set; }
public Variables TaskVariables { get; private set; }
public HashSet<string> OutputVariables => _outputvariables;
public IDictionary<String, Object> ExpressionValues { get; private set; }
public bool WriteDebug { get; private set; }
public List<string> PrependPath { get; private set; }
public ContainerInfo Container { get; private set; }
Expand Down Expand Up @@ -175,6 +179,7 @@ public IExecutionContext CreateChild(Guid recordId, string displayName, string r
child.Containers = Containers;
child.SecureFiles = SecureFiles;
child.TaskVariables = taskVariables;
child.ExpressionValues = ExpressionValues;
child._cancellationTokenSource = new CancellationTokenSource();
child.WriteDebug = WriteDebug;
child._parentExecutionContext = this;
Expand Down Expand Up @@ -270,26 +275,14 @@ public void SetVariable(string name, string value, bool isSecret = false, bool i
}
}

public void SetOutput(string name, string value, bool isSecret = false)
public void SetOutput(string name, string value, out string reference)
{
ArgUtil.NotNullOrEmpty(name, nameof(name));

if (OutputVariables.Contains(name))
{
_record.Variables[name] = new VariableValue()
{
Value = value,
IsSecret = isSecret
};
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
// todo: restrict multiline?

ArgUtil.NotNullOrEmpty(_record.RefName, nameof(_record.RefName));
Variables.Set($"{_record.RefName}.{name}", value, secret: isSecret);
}
else
{
throw new InvalidOperationException($"'{name}' is not defined as an output value.");
}
var actionsContext = ExpressionValues["actions"] as ActionsContext;
actionsContext.SetOutput(_record.RefName, name, value, out reference);
}

public void SetTimeout(TimeSpan? timeout)
Expand Down Expand Up @@ -426,6 +419,17 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation
List<string> warnings;
Variables = new Variables(HostContext, message.Variables, out warnings);

// Expression values
ExpressionValues = new Dictionary<String, Object>();
if (message.ExpressionValues?.Count > 0)
{
foreach (var pair in message.ExpressionValues)
{
ExpressionValues[pair.Key] = pair.Value;
}
}
ExpressionValues["actions"] = new ActionsContext();

// Prepend Path
PrependPath = new List<string>();

Expand Down Expand Up @@ -730,6 +734,37 @@ public static void Debug(this IExecutionContext context, string message)
context.Write(WellKnownTags.Debug, message);
}
}

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)
{
_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.Output(string.Format(CultureInfo.CurrentCulture, $"{WellKnownTags.Debug}{format}", args));
}

public void Verbose(string format, params Object[] args)
{
// // todo: switch to verbose? how to set system.debug?
// _executionContext.Output(string.Format(CultureInfo.CurrentCulture, $"{WellKnownTags.Debug}{format}", args));
}
}

public static class WellKnownTags
Expand Down
Loading

0 comments on commit 20edd46

Please sign in to comment.