GitHub Actions Runner

This commit is contained in:
Tingluo Huang
2019-10-10 00:52:42 -04:00
commit c8afc84840
1255 changed files with 198670 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
using System;
using System.IO;
namespace GitHub.Runner.Sdk
{
public static class ArgUtil
{
public static void Directory(string directory, string name)
{
ArgUtil.NotNullOrEmpty(directory, name);
if (!System.IO.Directory.Exists(directory))
{
throw new DirectoryNotFoundException(
message: $"Directory not found: '{directory}'");
}
}
public static void Equal<T>(T expected, T actual, string name)
{
if (object.ReferenceEquals(expected, actual))
{
return;
}
if (object.ReferenceEquals(expected, null) ||
!expected.Equals(actual))
{
throw new ArgumentOutOfRangeException(
paramName: name,
actualValue: actual,
message: $"{name} does not equal expected value. Expected '{expected}'. Actual '{actual}'.");
}
}
public static void File(string fileName, string name)
{
ArgUtil.NotNullOrEmpty(fileName, name);
if (!System.IO.File.Exists(fileName))
{
throw new FileNotFoundException(
message: $"File not found: '{fileName}'",
fileName: fileName);
}
}
public static void NotNull(object value, string name)
{
if (object.ReferenceEquals(value, null))
{
throw new ArgumentNullException(name);
}
}
public static void NotNullOrEmpty(string value, string name)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException(name);
}
}
public static void NotEmpty(Guid value, string name)
{
if (value == Guid.Empty)
{
throw new ArgumentNullException(name);
}
}
public static void Null(object value, string name)
{
if (!object.ReferenceEquals(value, null))
{
throw new ArgumentException(message: $"{name} should be null.", paramName: name);
}
}
}
}

View File

@@ -0,0 +1,467 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace GitHub.Runner.Sdk
{
public static class IOUtil
{
public static string ExeExtension
{
get
{
#if OS_WINDOWS
return ".exe";
#else
return string.Empty;
#endif
}
}
public static StringComparison FilePathStringComparison
{
get
{
#if OS_LINUX
return StringComparison.Ordinal;
#else
return StringComparison.OrdinalIgnoreCase;
#endif
}
}
public static void SaveObject(object obj, string path)
{
File.WriteAllText(path, StringUtil.ConvertToJson(obj), Encoding.UTF8);
}
public static T LoadObject<T>(string path)
{
string json = File.ReadAllText(path, Encoding.UTF8);
return StringUtil.ConvertFromJson<T>(json);
}
public static string GetPathHash(string path)
{
string hashString = path.ToLowerInvariant();
using (SHA256 sha256hash = SHA256.Create())
{
byte[] data = sha256hash.ComputeHash(Encoding.UTF8.GetBytes(hashString));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
string hash = sBuilder.ToString();
return hash;
}
}
public static void Delete(string path, CancellationToken cancellationToken)
{
DeleteDirectory(path, cancellationToken);
DeleteFile(path);
}
public static void DeleteDirectory(string path, CancellationToken cancellationToken)
{
DeleteDirectory(path, contentsOnly: false, continueOnContentDeleteError: false, cancellationToken: cancellationToken);
}
public static void DeleteDirectory(string path, bool contentsOnly, bool continueOnContentDeleteError, CancellationToken cancellationToken)
{
ArgUtil.NotNullOrEmpty(path, nameof(path));
DirectoryInfo directory = new DirectoryInfo(path);
if (!directory.Exists)
{
return;
}
if (!contentsOnly)
{
// Remove the readonly flag.
RemoveReadOnly(directory);
// Check if the directory is a reparse point.
if (directory.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
// Delete the reparse point directory and short-circuit.
directory.Delete();
return;
}
}
// Initialize a concurrent stack to store the directories. The directories
// cannot be deleted until the files are deleted.
var directories = new ConcurrentStack<DirectoryInfo>();
if (!contentsOnly)
{
directories.Push(directory);
}
// Create a new token source for the parallel query. The parallel query should be
// canceled after the first error is encountered. Otherwise the number of exceptions
// could get out of control for a large directory with access denied on every file.
using (var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
try
{
// Recursively delete all files and store all subdirectories.
Enumerate(directory, tokenSource)
.AsParallel()
.WithCancellation(tokenSource.Token)
.ForAll((FileSystemInfo item) =>
{
bool success = false;
try
{
// Remove the readonly attribute.
RemoveReadOnly(item);
// Check if the item is a file.
if (item is FileInfo)
{
// Delete the file.
item.Delete();
}
else
{
// Check if the item is a directory reparse point.
var subdirectory = item as DirectoryInfo;
ArgUtil.NotNull(subdirectory, nameof(subdirectory));
if (subdirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
try
{
// Delete the reparse point.
subdirectory.Delete();
}
catch (DirectoryNotFoundException)
{
// The target of the reparse point directory has been deleted.
// Therefore the item is no longer a directory and is now a file.
//
// Deletion of reparse point directories happens in parallel. This case can occur
// when reparse point directory FOO points to some other reparse point directory BAR,
// and BAR is deleted after the DirectoryInfo for FOO has already been initialized.
File.Delete(subdirectory.FullName);
}
}
else
{
// Store the directory.
directories.Push(subdirectory);
}
}
success = true;
}
catch (Exception) when (continueOnContentDeleteError)
{
// ignore any exception when continueOnContentDeleteError is true.
success = true;
}
finally
{
if (!success)
{
tokenSource.Cancel(); // Cancel is thread-safe.
}
}
});
}
catch (Exception)
{
tokenSource.Cancel();
throw;
}
}
// Delete the directories.
foreach (DirectoryInfo dir in directories.OrderByDescending(x => x.FullName.Length))
{
cancellationToken.ThrowIfCancellationRequested();
dir.Delete();
}
}
public static void DeleteFile(string path)
{
ArgUtil.NotNullOrEmpty(path, nameof(path));
var file = new FileInfo(path);
if (file.Exists)
{
RemoveReadOnly(file);
file.Delete();
}
}
public static void MoveDirectory(string sourceDir, string targetDir, string stagingDir, CancellationToken token)
{
ArgUtil.Directory(sourceDir, nameof(sourceDir));
ArgUtil.NotNullOrEmpty(targetDir, nameof(targetDir));
ArgUtil.NotNullOrEmpty(stagingDir, nameof(stagingDir));
// delete existing stagingDir
DeleteDirectory(stagingDir, token);
// make sure parent dir of stagingDir exist
Directory.CreateDirectory(Path.GetDirectoryName(stagingDir));
// move source to staging
Directory.Move(sourceDir, stagingDir);
// delete existing targetDir
DeleteDirectory(targetDir, token);
// make sure parent dir of targetDir exist
Directory.CreateDirectory(Path.GetDirectoryName(targetDir));
// move staging to target
Directory.Move(stagingDir, targetDir);
}
/// <summary>
/// Given a path and directory, return the path relative to the directory. If the path is not
/// under the directory the path is returned un modified. Examples:
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\src") -> @"project\foo.cpp"
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\specs") -> @"d:\src\project\foo.cpp"
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\src\proj") -> @"d:\src\project\foo.cpp"
/// </summary>
/// <remarks>Safe for remote paths. Does not access the local disk.</remarks>
/// <param name="path">Path to make relative.</param>
/// <param name="folder">Folder to make it relative to.</param>
/// <returns>Relative path.</returns>
public static string MakeRelative(string path, string folder)
{
ArgUtil.NotNullOrEmpty(path, nameof(path));
ArgUtil.NotNull(folder, nameof(folder));
// Replace all Path.AltDirectorySeparatorChar with Path.DirectorySeparatorChar from both inputs
path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
folder = folder.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Check if the dir is a prefix of the path (if not, it isn't relative at all).
if (!path.StartsWith(folder, IOUtil.FilePathStringComparison))
{
return path;
}
// Dir is a prefix of the path, if they are the same length then the relative path is empty.
if (path.Length == folder.Length)
{
return string.Empty;
}
// If the dir ended in a '\\' (like d:\) or '/' (like user/bin/) then we have a relative path.
if (folder.Length > 0 && folder[folder.Length - 1] == Path.DirectorySeparatorChar)
{
return path.Substring(folder.Length);
}
// The next character needs to be a '\\' or they aren't really relative.
else if (path[folder.Length] == Path.DirectorySeparatorChar)
{
return path.Substring(folder.Length + 1);
}
else
{
return path;
}
}
public static string ResolvePath(String rootPath, String relativePath)
{
ArgUtil.NotNullOrEmpty(rootPath, nameof(rootPath));
ArgUtil.NotNullOrEmpty(relativePath, nameof(relativePath));
if (!Path.IsPathRooted(rootPath))
{
throw new ArgumentException($"{rootPath} should be a rooted path.");
}
if (relativePath.IndexOfAny(Path.GetInvalidPathChars()) > -1)
{
throw new InvalidOperationException($"{relativePath} contains invalid path characters.");
}
else if (Path.GetFileName(relativePath).IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
{
throw new InvalidOperationException($"{relativePath} contains invalid folder name characters.");
}
else if (Path.IsPathRooted(relativePath))
{
throw new InvalidOperationException($"{relativePath} can not be a rooted path.");
}
else
{
rootPath = rootPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
relativePath = relativePath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
// Root the path
relativePath = String.Concat(rootPath, Path.AltDirectorySeparatorChar, relativePath);
// Collapse ".." directories with their parent, and skip "." directories.
String[] split = relativePath.Split(new[] { Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var segments = new Stack<String>(split.Length);
Int32 skip = 0;
for (Int32 i = split.Length - 1; i >= 0; i--)
{
String segment = split[i];
if (String.Equals(segment, ".", StringComparison.Ordinal))
{
continue;
}
else if (String.Equals(segment, "..", StringComparison.Ordinal))
{
skip++;
}
else if (skip > 0)
{
skip--;
}
else
{
segments.Push(segment);
}
}
if (skip > 0)
{
throw new InvalidOperationException($"The file path {relativePath} is invalid");
}
#if OS_WINDOWS
if (segments.Count > 1)
{
return String.Join(Path.DirectorySeparatorChar, segments);
}
else
{
return segments.Pop() + Path.DirectorySeparatorChar;
}
#else
return Path.DirectorySeparatorChar + String.Join(Path.DirectorySeparatorChar, segments);
#endif
}
}
public static void CopyDirectory(string source, string target, CancellationToken cancellationToken)
{
// Validate args.
ArgUtil.Directory(source, nameof(source));
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNull(cancellationToken, nameof(cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
// Create the target directory.
Directory.CreateDirectory(target);
// Get the file contents of the directory to copy.
DirectoryInfo sourceDir = new DirectoryInfo(source);
foreach (FileInfo sourceFile in sourceDir.GetFiles() ?? new FileInfo[0])
{
// Check if the file already exists.
cancellationToken.ThrowIfCancellationRequested();
FileInfo targetFile = new FileInfo(Path.Combine(target, sourceFile.Name));
if (!targetFile.Exists ||
sourceFile.Length != targetFile.Length ||
sourceFile.LastWriteTime != targetFile.LastWriteTime)
{
// Copy the file.
sourceFile.CopyTo(targetFile.FullName, true);
}
}
// Copy the subdirectories.
foreach (DirectoryInfo subDir in sourceDir.GetDirectories() ?? new DirectoryInfo[0])
{
CopyDirectory(
source: subDir.FullName,
target: Path.Combine(target, subDir.Name),
cancellationToken: cancellationToken);
}
}
public static void ValidateExecutePermission(string directory)
{
ArgUtil.Directory(directory, nameof(directory));
string dir = directory;
string failsafeString = Environment.GetEnvironmentVariable("AGENT_TEST_VALIDATE_EXECUTE_PERMISSIONS_FAILSAFE");
int failsafe;
if (string.IsNullOrEmpty(failsafeString) || !int.TryParse(failsafeString, out failsafe))
{
failsafe = 100;
}
for (int i = 0; i < failsafe; i++)
{
try
{
Directory.EnumerateFileSystemEntries(dir).FirstOrDefault();
}
catch (UnauthorizedAccessException ex)
{
// Permission to read the directory contents is required for '{0}' and each directory up the hierarchy. {1}
string message = $"Permission to read the directory contents is required for '{directory}' and each directory up the hierarchy. {ex.Message}";
throw new UnauthorizedAccessException(message, ex);
}
dir = Path.GetDirectoryName(dir);
if (string.IsNullOrEmpty(dir))
{
return;
}
}
// This should never happen.
throw new NotSupportedException($"Unable to validate execute permissions for directory '{directory}'. Exceeded maximum iterations.");
}
/// <summary>
/// Recursively enumerates a directory without following directory reparse points.
/// </summary>
private static IEnumerable<FileSystemInfo> Enumerate(DirectoryInfo directory, CancellationTokenSource tokenSource)
{
ArgUtil.NotNull(directory, nameof(directory));
ArgUtil.Equal(false, directory.Attributes.HasFlag(FileAttributes.ReparsePoint), nameof(directory.Attributes.HasFlag));
// Push the directory onto the processing stack.
var directories = new Stack<DirectoryInfo>(new[] { directory });
while (directories.Count > 0)
{
// Pop the next directory.
directory = directories.Pop();
foreach (FileSystemInfo item in directory.GetFileSystemInfos())
{
// Push non-reparse-point directories onto the processing stack.
directory = item as DirectoryInfo;
if (directory != null &&
!item.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
directories.Push(directory);
}
// Then yield the directory. Otherwise there is a race condition when this method attempts to initialize
// the Attributes and the caller is deleting the reparse point in parallel (FileNotFoundException).
yield return item;
}
}
}
private static void RemoveReadOnly(FileSystemInfo item)
{
ArgUtil.NotNull(item, nameof(item));
if (item.Attributes.HasFlag(FileAttributes.ReadOnly))
{
item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.IO;
namespace GitHub.Runner.Sdk
{
public static class PathUtil
{
#if OS_WINDOWS
public static readonly string PathVariable = "Path";
#else
public static readonly string PathVariable = "PATH";
#endif
public static string PrependPath(string path, string currentPath)
{
ArgUtil.NotNullOrEmpty(path, nameof(path));
if (string.IsNullOrEmpty(currentPath))
{
// Careful not to add a trailing separator if the PATH is empty.
// On OSX/Linux, a trailing separator indicates that "current directory"
// is added to the PATH, which is considered a security risk.
return path;
}
// Not prepend path if it is already the first path in %PATH%
if (currentPath.StartsWith(path + Path.PathSeparator, IOUtil.FilePathStringComparison))
{
return currentPath;
}
else
{
return path + Path.PathSeparator + currentPath;
}
}
}
}

View File

@@ -0,0 +1,126 @@
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
namespace GitHub.Runner.Sdk
{
public static class StringUtil
{
private static readonly object[] s_defaultFormatArgs = new object[] { null };
private static Lazy<JsonSerializerSettings> s_serializerSettings = new Lazy<JsonSerializerSettings>(() =>
{
var settings = new VssJsonMediaTypeFormatter().SerializerSettings;
settings.DateParseHandling = DateParseHandling.None;
settings.FloatParseHandling = FloatParseHandling.Double;
return settings;
});
static StringUtil()
{
#if OS_WINDOWS
// By default, only Unicode encodings, ASCII, and code page 28591 are supported.
// This line is required to support the full set of encodings that were included
// in Full .NET prior to 4.6.
//
// For example, on an en-US box, this is required for loading the encoding for the
// default console output code page '437'. Without loading the correct encoding for
// code page IBM437, some characters cannot be translated correctly, e.g. write 'ç'
// from powershell.exe.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
}
public static T ConvertFromJson<T>(string value)
{
return JsonConvert.DeserializeObject<T>(value, s_serializerSettings.Value);
}
/// <summary>
/// Convert String to boolean, valid true string: "1", "true", "$true", valid false string: "0", "false", "$false".
/// </summary>
/// <param name="value">value to convert.</param>
/// <param name="defaultValue">default result when value is null or empty or not a valid true/false string.</param>
/// <returns></returns>
public static bool ConvertToBoolean(string value, bool defaultValue = false)
{
if (string.IsNullOrEmpty(value))
{
return defaultValue;
}
switch (value.ToLowerInvariant())
{
case "1":
case "true":
case "$true":
return true;
case "0":
case "false":
case "$false":
return false;
default:
return defaultValue;
}
}
public static string ConvertToJson(object obj, Formatting formatting = Formatting.Indented)
{
return JsonConvert.SerializeObject(obj, formatting, s_serializerSettings.Value);
}
public static void EnsureRegisterEncodings()
{
// The static constructor should have registered the required encodings.
}
public static string Format(string format, params object[] args)
{
return Format(CultureInfo.InvariantCulture, format, args);
}
public static Encoding GetSystemEncoding()
{
#if OS_WINDOWS
// The static constructor should have registered the required encodings.
// Code page 0 is equivalent to the current system default (i.e. CP_ACP).
// E.g. code page 1252 on an en-US box.
return Encoding.GetEncoding(0);
#else
throw new NotSupportedException(nameof(GetSystemEncoding)); // Should never reach here.
#endif
}
private static string Format(CultureInfo culture, string format, params object[] args)
{
try
{
// 1) Protect against argument null exception for the format parameter.
// 2) Protect against argument null exception for the args parameter.
// 3) Coalesce null or empty args with an array containing one null element.
// This protects against format exceptions where string.Format thinks
// that not enough arguments were supplied, even though the intended arg
// literally is null or an empty array.
return string.Format(
culture,
format ?? string.Empty,
args == null || args.Length == 0 ? s_defaultFormatArgs : args);
}
catch (FormatException)
{
// TODO: Log that string format failed. Consider moving this into a context base class if that's the only place it's used. Then the current trace scope would be available as well.
if (args != null)
{
return string.Format(culture, "{0} {1}", format, string.Join(", ", args));
}
return format;
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace GitHub.Runner.Sdk
{
public static class UrlUtil
{
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
{
ArgUtil.NotNull(baseUrl, nameof(baseUrl));
// return baseurl when there is no username and password
if (string.IsNullOrEmpty(username) && string.IsNullOrEmpty(password))
{
return baseUrl;
}
UriBuilder credUri = new UriBuilder(baseUrl);
// ensure we have a username, uribuild will throw if username is empty but password is not.
if (string.IsNullOrEmpty(username))
{
username = "emptyusername";
}
// escape chars in username for uri
credUri.UserName = Uri.EscapeDataString(username);
// escape chars in password for uri
if (!string.IsNullOrEmpty(password))
{
credUri.Password = Uri.EscapeDataString(password);
}
return credUri.Uri;
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using GitHub.DistributedTask.WebApi;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using GitHub.Services.OAuth;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Net;
namespace GitHub.Runner.Sdk
{
public static class VssUtil
{
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy, IVssClientCertificateManager clientCert)
{
var headerValues = new List<ProductInfoHeaderValue>();
headerValues.Add(additionalUserAgent);
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
{
headerValues.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
VssClientHttpRequestSettings.Default.ClientCertificateManager = clientCert;
#if OS_LINUX || OS_OSX
// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
// This causes problems with some versions of the Curl handler.
// See GitHub issue https://github.com/dotnet/corefx/issues/32376
VssClientHttpRequestSettings.Default.UseHttp11 = true;
#endif
VssHttpMessageHandler.DefaultWebProxy = proxy;
}
public static VssConnection CreateConnection(Uri serverUri, VssCredentials credentials, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, TimeSpan? timeout = null)
{
VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone();
int maxRetryRequest;
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY") ?? string.Empty, out maxRetryRequest))
{
maxRetryRequest = 3;
}
// make sure MaxRetryRequest in range [3, 10]
settings.MaxRetryRequest = Math.Min(Math.Max(maxRetryRequest, 3), 10);
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT") ?? string.Empty, out int httpRequestTimeoutSeconds))
{
settings.SendTimeout = timeout ?? TimeSpan.FromSeconds(100);
}
else
{
// prefer environment variable
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
}
// Remove Invariant from the list of accepted languages.
//
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
// UI culture to the list of accepted languages. The UI culture will be Invariant on OSX/Linux when the
// LANG environment variable is not set when the program starts. If Invariant is in the list of accepted
// languages, then "System.ArgumentException: The value cannot be null or empty." will be thrown when the
// settings are applied to an HttpRequestMessage.
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
VssConnection connection = new VssConnection(serverUri, new VssHttpMessageHandler(credentials, settings), additionalDelegatingHandler);
return connection;
}
public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
{
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));
if (serviceEndpoint.Authorization.Parameters.Count == 0)
{
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
}
VssCredentials credentials = null;
string accessToken;
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
{
credentials = new VssCredentials(null, new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
}
return credentials;
}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.IO;
using System.Linq;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Sdk
{
public static class WhichUtil
{
public static string Which(string command, bool require = false, ITraceWriter trace = null)
{
ArgUtil.NotNullOrEmpty(command, nameof(command));
trace?.Info($"Which: '{command}'");
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
if (string.IsNullOrEmpty(path))
{
trace?.Info("PATH environment variable not defined.");
path = path ?? string.Empty;
}
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < pathSegments.Length; i++)
{
pathSegments[i] = Environment.ExpandEnvironmentVariables(pathSegments[i]);
}
foreach (string pathSegment in pathSegments)
{
if (!string.IsNullOrEmpty(pathSegment) && Directory.Exists(pathSegment))
{
string[] matches = null;
#if OS_WINDOWS
string pathExt = Environment.GetEnvironmentVariable("PATHEXT");
if (string.IsNullOrEmpty(pathExt))
{
// XP's system default value for PATHEXT system variable
pathExt = ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh";
}
string[] pathExtSegments = pathExt.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
// if command already has an extension.
if (pathExtSegments.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
{
try
{
matches = Directory.GetFiles(pathSegment, command);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0)
{
trace?.Info($"Location: '{matches.First()}'");
return matches.First();
}
}
else
{
string searchPattern;
searchPattern = StringUtil.Format($"{command}.*");
try
{
matches = Directory.GetFiles(pathSegment, searchPattern);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0)
{
// add extension.
for (int i = 0; i < pathExtSegments.Length; i++)
{
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)))
{
trace?.Info($"Location: '{fullPath}'");
return fullPath;
}
}
}
}
#else
try
{
matches = Directory.GetFiles(pathSegment, command);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0)
{
trace?.Info($"Location: '{matches.First()}'");
return matches.First();
}
#endif
}
}
trace?.Info("Not found.");
if (require)
{
throw new FileNotFoundException(
message: $"File not found: '{command}'",
fileName: command);
}
return null;
}
}
}