2.296.2 Release notes (#2107)

* fix issue with env's overwriting environment

* add release notes

* handle value escaping

* compile regex for runtime perf improvements
This commit is contained in:
Thomas Boop
2022-09-08 13:36:38 -04:00
committed by GitHub
parent 219852abcb
commit 96f4f5e76e
5 changed files with 64 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
## Bugs ## Bugs
- Fixed an issue where job and service container envs were corrupted (#2091) - Fixed an issue where self hosted environments had their docker env's overwritten (#2107)
## Misc ## Misc
## Windows x64 ## Windows x64

View File

@@ -107,7 +107,6 @@ namespace GitHub.Runner.Worker.Container
public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container) public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container)
{ {
IList<string> dockerOptions = new List<string>(); IList<string> dockerOptions = new List<string>();
IDictionary<string, string> environment = new Dictionary<string, string>();
// OPTIONS // OPTIONS
dockerOptions.Add($"--name {container.ContainerDisplayName}"); dockerOptions.Add($"--name {container.ContainerDisplayName}");
dockerOptions.Add($"--label {DockerInstanceLabel}"); dockerOptions.Add($"--label {DockerInstanceLabel}");
@@ -136,8 +135,7 @@ namespace GitHub.Runner.Worker.Container
} }
else else
{ {
environment.Add(env.Key, env.Value); dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key, env.Value));
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
} }
} }
@@ -185,7 +183,7 @@ namespace GitHub.Runner.Worker.Container
dockerOptions.Add($"{container.ContainerEntryPointArgs}"); dockerOptions.Add($"{container.ContainerEntryPointArgs}");
var optionsString = string.Join(" ", dockerOptions); var optionsString = string.Join(" ", dockerOptions);
List<string> outputStrings = await ExecuteDockerCommandAsync(context, "create", optionsString, environment); List<string> outputStrings = await ExecuteDockerCommandAsync(context, "create", optionsString);
return outputStrings.FirstOrDefault(); return outputStrings.FirstOrDefault();
} }
@@ -445,11 +443,6 @@ namespace GitHub.Runner.Worker.Container
} }
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options) private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options)
{
return await ExecuteDockerCommandAsync(context, command, options, null);
}
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options, IDictionary<string, string> environment)
{ {
string arg = $"{command} {options}".Trim(); string arg = $"{command} {options}".Trim();
context.Command($"{DockerPath} {arg}"); context.Command($"{DockerPath} {arg}");
@@ -477,7 +470,7 @@ namespace GitHub.Runner.Worker.Container
workingDirectory: context.GetGitHubContext("workspace"), workingDirectory: context.GetGitHubContext("workspace"),
fileName: DockerPath, fileName: DockerPath,
arguments: arg, arguments: arg,
environment: environment, environment: null,
requireExitCodeZero: true, requireExitCodeZero: true,
outputEncoding: null, outputEncoding: null,
cancellationToken: CancellationToken.None); cancellationToken: CancellationToken.None);

View File

@@ -6,6 +6,9 @@ namespace GitHub.Runner.Worker.Container
{ {
public class DockerUtil public class DockerUtil
{ {
private static readonly Regex QuoteEscape = new Regex(@"(\\*)" + "\"", RegexOptions.Compiled);
private static readonly Regex EndOfStringEscape = new Regex(@"(\\+)$", RegexOptions.Compiled);
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines) public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
{ {
const string targetPort = "targetPort"; const string targetPort = "targetPort";
@@ -68,12 +71,37 @@ namespace GitHub.Runner.Worker.Container
{ {
return ""; return "";
} }
return $"{flag} \"{EscapeString(key)}\""; return $"{flag} {EscapeString(key)}";
}
public static string CreateEscapedOption(string flag, string key, string value)
{
if (String.IsNullOrEmpty(key))
{
return "";
}
var escapedString = EscapeString($"{key}={value}");
return $"{flag} {escapedString}";
} }
private static string EscapeString(string value) private static string EscapeString(string value)
{ {
return value.Replace("\\", "\\\\").Replace("\"", "\\\""); if (String.IsNullOrEmpty(value))
{
return "";
}
// Dotnet escaping rules are weird here, we can only escape \ if it precedes a "
// If a double quotation mark follows two or an even number of backslashes, each proceeding backslash pair is replaced with one backslash and the double quotation mark is removed.
// If a double quotation mark follows an odd number of backslashes, including just one, each preceding pair is replaced with one backslash and the remaining backslash is removed; however, in this case the double quotation mark is not removed.
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getcommandlineargs?redirectedfrom=MSDN&view=net-6.0#remarks
// First, find any \ followed by a " and double the number of \ + 1.
value = QuoteEscape.Replace(value, @"$1$1\" + "\"");
// Next, what if it ends in `\`, it would escape the end quote. So, we need to detect that at the end of the string and perform the same escape
// Luckily, we can just use the $ character with detects the end of string in regex
value = EndOfStringEscape.Replace(value, @"$1$1");
// Finally, wrap it in quotes
return $"\"{value}\"";
} }
} }
} }

View File

@@ -149,13 +149,12 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
[InlineData("", "")] [InlineData("", "")]
[InlineData("HOME alpine:3.8 sh -c id #", "HOME alpine:3.8 sh -c id #")] [InlineData("foo", "foo")]
[InlineData("HOME \"alpine:3.8 sh -c id #", "HOME \\\"alpine:3.8 sh -c id #")] [InlineData("foo \\ bar", "foo \\ bar")]
[InlineData("HOME \\\"alpine:3.8 sh -c id #", "HOME \\\\\\\"alpine:3.8 sh -c id #")] [InlineData("foo \\", "foo \\\\")]
[InlineData("HOME \\\\\"alpine:3.8 sh -c id #", "HOME \\\\\\\\\\\"alpine:3.8 sh -c id #")] [InlineData("foo \\\\", "foo \\\\\\\\")]
[InlineData("HOME \"\"alpine:3.8 sh -c id #", "HOME \\\"\\\"alpine:3.8 sh -c id #")] [InlineData("foo \\\" bar", "foo \\\\\\\" bar")]
[InlineData("HOME \\\"\"alpine:3.8 sh -c id #", "HOME \\\\\\\"\\\"alpine:3.8 sh -c id #")] [InlineData("foo \\\\\" bar", "foo \\\\\\\\\\\" bar")]
[InlineData("HOME \"\\\"alpine:3.8 sh -c id #", "HOME \\\"\\\\\\\"alpine:3.8 sh -c id #")]
public void CreateEscapedOption_keyOnly(string input, string escaped) public void CreateEscapedOption_keyOnly(string input, string escaped)
{ {
var flag = "--example"; var flag = "--example";
@@ -171,5 +170,28 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
} }
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData("foo", "bar", "foo=bar")]
[InlineData("foo\\", "bar", "foo\\=bar")]
[InlineData("foo\\", "bar\\", "foo\\=bar\\\\")]
[InlineData("foo \\","bar \\", "foo \\=bar \\\\")]
public void CreateEscapedOption_keyValue(string keyInput, string valueInput, string escapedString)
{
var flag = "--example";
var actual = DockerUtil.CreateEscapedOption(flag, keyInput, valueInput);
string expected;
if (String.IsNullOrEmpty(keyInput))
{
expected = "";
}
else
{
expected = $"{flag} \"{escapedString}\"";
}
Assert.Equal(expected, actual);
}
} }
} }

View File

@@ -1 +1 @@
2.296.1 2.296.2