Skip to content

Commit

Permalink
Add file commands for save-state and set-output (#2118)
Browse files Browse the repository at this point in the history
  • Loading branch information
rentziass authored Sep 26, 2022
1 parent 0678e8d commit 15cbadb
Show file tree
Hide file tree
Showing 5 changed files with 1,085 additions and 109 deletions.
2 changes: 2 additions & 0 deletions src/Runner.Common/ExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ private List<IExtension> LoadExtensions<T>() where T : class, IExtension
Add<T>(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.CreateStepSummaryCommand, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.SaveStateFileCommand, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.SetOutputFileCommand, Runner.Worker");
break;
case "GitHub.Runner.Listener.Check.ICheckExtension":
Add<T>(extensions, "GitHub.Runner.Listener.Check.InternetCheck, Runner.Listener");
Expand Down
308 changes: 199 additions & 109 deletions src/Runner.Worker/FileCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,74 +138,10 @@ public sealed class SetEnvFileCommand : RunnerService, IFileCommandExtension

public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
{
try
{
var text = File.ReadAllText(filePath) ?? string.Empty;
var index = 0;
var line = ReadLine(text, ref index);
while (line != null)
{
if (!string.IsNullOrEmpty(line))
{
var equalsIndex = line.IndexOf("=", StringComparison.Ordinal);
var heredocIndex = line.IndexOf("<<", StringComparison.Ordinal);

// Normal style NAME=VALUE
if (equalsIndex >= 0 && (heredocIndex < 0 || equalsIndex < heredocIndex))
{
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
if (string.IsNullOrEmpty(line))
{
throw new Exception($"Invalid environment variable format '{line}'. Environment variable name must not be empty");
}
SetEnvironmentVariable(context, split[0], split[1]);
}
// Heredoc style NAME<<EOF
else if (heredocIndex >= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex))
{
var split = line.Split(new[] { "<<" }, 2, StringSplitOptions.None);
if (string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1]))
{
throw new Exception($"Invalid environment variable format '{line}'. Environment variable name must not be empty and delimiter must not be empty");
}
var name = split[0];
var delimiter = split[1];
var startIndex = index; // Start index of the value (inclusive)
var endIndex = index; // End index of the value (exclusive)
var tempLine = ReadLine(text, ref index, out var newline);
while (!string.Equals(tempLine, delimiter, StringComparison.Ordinal))
{
if (tempLine == null)
{
throw new Exception($"Invalid environment variable value. Matching delimiter not found '{delimiter}'");
}
if (newline == null)
{
throw new Exception($"Invalid environment variable value. EOF marker missing new line.");
}
endIndex = index - newline.Length;
tempLine = ReadLine(text, ref index, out newline);
}

var value = endIndex > startIndex ? text.Substring(startIndex, endIndex - startIndex) : string.Empty;
SetEnvironmentVariable(context, name, value);
}
else
{
throw new Exception($"Invalid environment variable format '{line}'");
}
}

line = ReadLine(text, ref index);
}
}
catch (DirectoryNotFoundException)
var pairs = new EnvFileKeyValuePairs(context, filePath);
foreach (var pair in pairs)
{
context.Debug($"Environment variables file does not exist '{filePath}'");
}
catch (FileNotFoundException)
{
context.Debug($"Environment variables file does not exist '{filePath}'");
SetEnvironmentVariable(context, pair.Key, pair.Value);
}
}

Expand All @@ -218,48 +154,6 @@ private static void SetEnvironmentVariable(
context.SetEnvContext(name, value);
context.Debug($"{name}='{value}'");
}

private static string ReadLine(
string text,
ref int index)
{
return ReadLine(text, ref index, out _);
}

private static string ReadLine(
string text,
ref int index,
out string newline)
{
if (index >= text.Length)
{
newline = null;
return null;
}

var originalIndex = index;
var lfIndex = text.IndexOf("\n", index, StringComparison.Ordinal);
if (lfIndex < 0)
{
index = text.Length;
newline = null;
return text.Substring(originalIndex);
}

#if OS_WINDOWS
var crLFIndex = text.IndexOf("\r\n", index, StringComparison.Ordinal);
if (crLFIndex >= 0 && crLFIndex < lfIndex)
{
index = crLFIndex + 2; // Skip over CRLF
newline = "\r\n";
return text.Substring(originalIndex, crLFIndex - originalIndex);
}
#endif

index = lfIndex + 1; // Skip over LF
newline = "\n";
return text.Substring(originalIndex, lfIndex - originalIndex);
}
}

public sealed class CreateStepSummaryCommand : RunnerService, IFileCommandExtension
Expand Down Expand Up @@ -325,4 +219,200 @@ public void ProcessCommand(IExecutionContext context, string filePath, Container
}
}
}

public sealed class SaveStateFileCommand : RunnerService, IFileCommandExtension
{
public string ContextName => "state";
public string FilePrefix => "save_state_";

public Type ExtensionType => typeof(IFileCommandExtension);

public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
{
var pairs = new EnvFileKeyValuePairs(context, filePath);
foreach (var pair in pairs)
{
// 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][pair.Key] = pair.Value;
}
// Otherwise modify the ExecutionContext
else
{
context.IntraActionState[pair.Key] = pair.Value;
}

context.Debug($"Save intra-action state {pair.Key} = {pair.Value}");
}
}
}

public sealed class SetOutputFileCommand : RunnerService, IFileCommandExtension
{
public string ContextName => "output";
public string FilePrefix => "set_output_";

public Type ExtensionType => typeof(IFileCommandExtension);

public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
{
var pairs = new EnvFileKeyValuePairs(context, filePath);
foreach (var pair in pairs)
{
context.SetOutput(pair.Key, pair.Value, out var reference);
context.Debug($"Set output {pair.Key} = {pair.Value}");
}
}
}

public sealed class EnvFileKeyValuePairs: IEnumerable<KeyValuePair<string, string>>
{
private IExecutionContext _context;
private string _filePath;

public EnvFileKeyValuePairs(IExecutionContext context, string filePath)
{
_context = context;
_filePath = filePath;
}

public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
var text = string.Empty;
try
{
text = File.ReadAllText(_filePath) ?? string.Empty;
}
catch (DirectoryNotFoundException)
{
_context.Debug($"File does not exist '{_filePath}'");
yield break;
}
catch (FileNotFoundException)
{
_context.Debug($"File does not exist '{_filePath}'");
yield break;
}

var index = 0;
var line = ReadLine(text, ref index);
while (line != null)
{
if (!string.IsNullOrEmpty(line))
{
var key = string.Empty;
var output = string.Empty;

var equalsIndex = line.IndexOf("=", StringComparison.Ordinal);
var heredocIndex = line.IndexOf("<<", StringComparison.Ordinal);

// Normal style NAME=VALUE
if (equalsIndex >= 0 && (heredocIndex < 0 || equalsIndex < heredocIndex))
{
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
if (string.IsNullOrEmpty(line))
{
throw new Exception($"Invalid format '{line}'. Name must not be empty");
}

key = split[0];
output = split[1];
}

// Heredoc style NAME<<EOF
else if (heredocIndex >= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex))
{
var split = line.Split(new[] { "<<" }, 2, StringSplitOptions.None);
if (string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1]))
{
throw new Exception($"Invalid format '{line}'. Name must not be empty and delimiter must not be empty");
}
key = split[0];
var delimiter = split[1];
var startIndex = index; // Start index of the value (inclusive)
var endIndex = index; // End index of the value (exclusive)
var tempLine = ReadLine(text, ref index, out var newline);
while (!string.Equals(tempLine, delimiter, StringComparison.Ordinal))
{
if (tempLine == null)
{
throw new Exception($"Invalid value. Matching delimiter not found '{delimiter}'");
}
if (newline == null)
{
throw new Exception($"Invalid value. EOF marker missing new line.");
}
endIndex = index - newline.Length;
tempLine = ReadLine(text, ref index, out newline);
}

output = endIndex > startIndex ? text.Substring(startIndex, endIndex - startIndex) : string.Empty;
}
else
{
throw new Exception($"Invalid format '{line}'");
}

yield return new KeyValuePair<string, string>(key, output);
}

line = ReadLine(text, ref index);
}
}

System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
// Invoke IEnumerator<KeyValuePair<string, string>> GetEnumerator() above.
return GetEnumerator();
}

private static string ReadLine(
string text,
ref int index)
{
return ReadLine(text, ref index, out _);
}

private static string ReadLine(
string text,
ref int index,
out string newline)
{
if (index >= text.Length)
{
newline = null;
return null;
}

var originalIndex = index;
var lfIndex = text.IndexOf("\n", index, StringComparison.Ordinal);
if (lfIndex < 0)
{
index = text.Length;
newline = null;
return text.Substring(originalIndex);
}

#if OS_WINDOWS
var crLFIndex = text.IndexOf("\r\n", index, StringComparison.Ordinal);
if (crLFIndex >= 0 && crLFIndex < lfIndex)
{
index = crLFIndex + 2; // Skip over CRLF
newline = "\r\n";
return text.Substring(originalIndex, crLFIndex - originalIndex);
}
#endif

index = lfIndex + 1; // Skip over LF
newline = "\n";
return text.Substring(originalIndex, lfIndex - originalIndex);
}
}
}
2 changes: 2 additions & 0 deletions src/Runner.Worker/GitHubContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public sealed class GitHubContext : DictionaryContextData, IEnvironmentContextDa
"graphql_url",
"head_ref",
"job",
"output",
"path",
"ref_name",
"ref_protected",
Expand All @@ -34,6 +35,7 @@ public sealed class GitHubContext : DictionaryContextData, IEnvironmentContextDa
"run_number",
"server_url",
"sha",
"state",
"step_summary",
"triggering_actor",
"workflow",
Expand Down
Loading

0 comments on commit 15cbadb

Please sign in to comment.