Compare commits

...

1 Commits

Author SHA1 Message Date
PJ Quirk
4a33e2c29d Revert "Use the API_URL and munge action URLs for GHES (#437)"
This reverts commit f798f5606b.
2020-04-24 10:41:29 -04:00
6 changed files with 105 additions and 427 deletions

View File

@@ -1,18 +1,19 @@
using System; using GitHub.Runner.Common.Util;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Diagnostics;
using System.Net.Http;
using System.Diagnostics.Tracing;
using GitHub.DistributedTask.Logging; using GitHub.DistributedTask.Logging;
using System.Net.Http.Headers;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common namespace GitHub.Runner.Common
@@ -614,8 +615,9 @@ namespace GitHub.Runner.Common
{ {
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context) public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
{ {
var handlerFactory = context.GetService<IHttpClientHandlerFactory>(); HttpClientHandler clientHandler = new HttpClientHandler();
return handlerFactory.CreateClientHandler(context.WebProxy); clientHandler.Proxy = context.WebProxy;
return clientHandler;
} }
} }

View File

@@ -1,19 +0,0 @@
using System.Net.Http;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(HttpClientHandlerFactory))]
public interface IHttpClientHandlerFactory : IRunnerService
{
HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy);
}
public class HttpClientHandlerFactory : RunnerService, IHttpClientHandlerFactory
{
public HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy)
{
return new HttpClientHandler() { Proxy = webProxy };
}
}
}

View File

@@ -1,19 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net; using System.Threading;
using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Services.Common; using GitHub.Services.Common;
using Newtonsoft.Json;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
@@ -71,7 +73,13 @@ namespace GitHub.Runner.Worker
} }
// Clear the cache (for self-hosted runners) // Clear the cache (for self-hosted runners)
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken); // Note, temporarily avoid this step for the on-premises product, to avoid rate limiting.
var configurationStore = HostContext.GetService<IConfigurationStore>();
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
{
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
}
foreach (var action in actions) foreach (var action in actions)
{ {
@@ -482,7 +490,7 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref)); ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref); string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
string watermarkFile = GetWatermarkFilePath(destDirectory); string watermarkFile = destDirectory + ".completed";
if (File.Exists(watermarkFile)) if (File.Exists(watermarkFile))
{ {
executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'."); executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'.");
@@ -496,84 +504,27 @@ namespace GitHub.Runner.Worker
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'"); executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
} }
var configurationStore = HostContext.GetService<IConfigurationStore>();
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
{
string apiUrl = GetApiUrl(executionContext);
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
await DownloadRepositoryActionAsync(executionContext, archiveLink, destDirectory);
return;
}
else
{
string apiUrl = GetApiUrl(executionContext);
// URLs to try:
var archiveLinks = new List<string> {
// A built-in action or an action the user has created, on their GHES instance
// Example: https://my-ghes/api/v3/repos/my-org/my-action/tarball/v1
BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
// A community action, synced to their GHES instance
// Example: https://my-ghes/api/v3/repos/actions-community/some-org-some-action/tarball/v1
BuildLinkToActionArchive(apiUrl, $"actions-community/{repositoryReference.Name.Replace("/", "-")}", repositoryReference.Ref)
};
foreach (var archiveLink in archiveLinks)
{
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
try
{
await DownloadRepositoryActionAsync(executionContext, archiveLink, destDirectory);
return;
}
catch (ActionNotFoundException)
{
Trace.Info($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}' at {archiveLink}");
continue;
}
}
throw new ActionNotFoundException($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}'. Paths attempted: {string.Join(", ", archiveLinks)}");
}
}
private string GetApiUrl(IExecutionContext executionContext)
{
string apiUrl = executionContext.GetGitHubContext("api_url");
if (!string.IsNullOrEmpty(apiUrl))
{
return apiUrl;
}
// Once the api_url is set for hosted, we can remove this fallback (it doesn't make sense for GHES)
return "https://api.github.com";
}
private static string BuildLinkToActionArchive(string apiUrl, string repository, string @ref)
{
#if OS_WINDOWS #if OS_WINDOWS
return $"{apiUrl}/repos/{repository}/zipball/{@ref}"; string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/zipball/{repositoryReference.Ref}";
#else #else
return $"{apiUrl}/repos/{repository}/tarball/{@ref}"; string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/tarball/{repositoryReference.Ref}";
#endif #endif
} Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, string link, string destDirectory)
{
//download and extract action in a temp folder and rename it on success //download and extract action in a temp folder and rename it on success
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid()); string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
Directory.CreateDirectory(tempDirectory); Directory.CreateDirectory(tempDirectory);
#if OS_WINDOWS #if OS_WINDOWS
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
#else #else
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
#endif #endif
Trace.Info($"Save archive '{archiveLink}' into {archiveFile}.");
Trace.Info($"Save archive '{link}' into {archiveFile}.");
try try
{ {
int retryCount = 0; int retryCount = 0;
// Allow up to 20 * 60s for any action to be downloaded from github graph. // Allow up to 20 * 60s for any action to be downloaded from github graph.
@@ -590,76 +541,64 @@ namespace GitHub.Runner.Worker
using (var httpClientHandler = HostContext.CreateHttpClientHandler()) using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler)) using (var httpClient = new HttpClient(httpClientHandler))
{ {
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN"); var configurationStore = HostContext.GetService<IConfigurationStore>();
if (string.IsNullOrEmpty(authToken)) var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
{ {
// TODO: Deprecate the PREVIEW_ACTION_TOKEN var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN"); if (string.IsNullOrEmpty(authToken))
}
if (!string.IsNullOrEmpty(authToken))
{
HostContext.SecretMasker.AddValue(authToken);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
else
{
var accessToken = executionContext.GetGitHubContext("token");
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
using (var response = await httpClient.GetAsync(link))
{
if (response.IsSuccessStatusCode)
{ {
using (var result = await response.Content.ReadAsStreamAsync()) // TODO: Deprecate the PREVIEW_ACTION_TOKEN
{ authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
await fs.FlushAsync(actionDownloadCancellation.Token);
// download succeed, break out the retry loop.
break;
}
} }
else if (response.StatusCode == HttpStatusCode.NotFound)
if (!string.IsNullOrEmpty(authToken))
{ {
// It doesn't make sense to retry in this case, so just stop HostContext.SecretMasker.AddValue(authToken);
throw new ActionNotFoundException(new Uri(link)); var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
} }
else else
{ {
// Something else bad happened, let's go to our retry logic var accessToken = executionContext.GetGitHubContext("token");
response.EnsureSuccessStatusCode(); var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
} }
} }
else
{
// Intentionally empty. Temporary for GHES alpha release, download from dotcom unauthenticated.
}
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
using (var result = await httpClient.GetStreamAsync(archiveLink))
{
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
await fs.FlushAsync(actionDownloadCancellation.Token);
// download succeed, break out the retry loop.
break;
}
} }
} }
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested) catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
{ {
Trace.Info("Action download has been cancelled."); Trace.Info($"Action download has been cancelled.");
throw;
}
catch (ActionNotFoundException)
{
Trace.Info($"The action at '{link}' does not exist");
throw; throw;
} }
catch (Exception ex) when (retryCount < 2) catch (Exception ex) when (retryCount < 2)
{ {
retryCount++; retryCount++;
Trace.Error($"Fail to download archive '{link}' -- Attempt: {retryCount}"); Trace.Error($"Fail to download archive '{archiveLink}' -- Attempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
if (actionDownloadTimeout.Token.IsCancellationRequested) if (actionDownloadTimeout.Token.IsCancellationRequested)
{ {
// action download didn't finish within timeout // action download didn't finish within timeout
executionContext.Warning($"Action '{link}' didn't finish download within {timeoutSeconds} seconds."); executionContext.Warning($"Action '{archiveLink}' didn't finish download within {timeoutSeconds} seconds.");
} }
else else
{ {
executionContext.Warning($"Failed to download action '{link}'. Error: {ex.Message}"); executionContext.Warning($"Failed to download action '{archiveLink}'. Error {ex.Message}");
} }
} }
} }
@@ -673,7 +612,7 @@ namespace GitHub.Runner.Worker
} }
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile)); ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
executionContext.Debug($"Download '{link}' to '{archiveFile}'"); executionContext.Debug($"Download '{archiveLink}' to '{archiveFile}'");
var stagingDirectory = Path.Combine(tempDirectory, "_staging"); var stagingDirectory = Path.Combine(tempDirectory, "_staging");
Directory.CreateDirectory(stagingDirectory); Directory.CreateDirectory(stagingDirectory);
@@ -723,7 +662,6 @@ namespace GitHub.Runner.Worker
} }
Trace.Verbose("Create watermark file indicate action download succeed."); Trace.Verbose("Create watermark file indicate action download succeed.");
string watermarkFile = GetWatermarkFilePath(destDirectory);
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString()); File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'."); executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
@@ -748,8 +686,6 @@ namespace GitHub.Runner.Worker
} }
} }
private string GetWatermarkFilePath(string directory) => directory + ".completed";
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction) private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
{ {
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference; var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
@@ -995,3 +931,4 @@ namespace GitHub.Runner.Worker
public string ActionRepository { get; set; } public string ActionRepository { get; set; }
} }
} }

View File

@@ -1,33 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace GitHub.Runner.Worker
{
public class ActionNotFoundException : Exception
{
public ActionNotFoundException(Uri actionUri)
: base(FormatMessage(actionUri))
{
}
public ActionNotFoundException(string message)
: base(message)
{
}
public ActionNotFoundException(string message, System.Exception inner)
: base(message, inner)
{
}
protected ActionNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
private static string FormatMessage(Uri actionUri)
{
return $"An action could not be found at the URI '{actionUri}'";
}
}
}

View File

@@ -16,9 +16,7 @@ namespace GitHub.Runner.Common.Tests
private static readonly List<string> SkippedFiles = new List<string>() private static readonly List<string> SkippedFiles = new List<string>()
{ {
"Runner.Common\\HostContext.cs", "Runner.Common\\HostContext.cs",
"Runner.Common/HostContext.cs", "Runner.Common/HostContext.cs"
"Runner.Common\\HttpClientHandlerFactory.cs",
"Runner.Common/HttpClientHandlerFactory.cs"
}; };
[Fact] [Fact]

View File

@@ -1,21 +1,19 @@
using System; using GitHub.DistributedTask.Expressions2;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk; using GitHub.Runner.Common.Util;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using Moq; using Moq;
using Moq.Protected; using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit; using Xunit;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -116,175 +114,47 @@ namespace GitHub.Runner.Common.Tests.Worker
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
public async void PrepareActions_DownloadBuiltInActionFromGraph_OnPremises() public async void PrepareActions_SkipDownloadActionFromGraphWhenCached_OnPremises()
{ {
try try
{ {
// Arrange // Arrange
Setup(); Setup();
const string ActionName = "actions/sample-action"; var actionId = Guid.NewGuid();
var actions = new List<Pipelines.ActionStep> var actions = new List<Pipelines.ActionStep>
{ {
new Pipelines.ActionStep() new Pipelines.ActionStep()
{ {
Name = "action", Name = "action",
Id = Guid.NewGuid(), Id = actionId,
Reference = new Pipelines.RepositoryPathReference() Reference = new Pipelines.RepositoryPathReference()
{ {
Name = ActionName, Name = "actions/no-such-action",
Ref = "master", Ref = "master",
RepositoryType = "GitHub" RepositoryType = "GitHub"
} }
} }
}; };
// Return a valid action from GHES via mock
const string ApiUrl = "https://ghes.example.com/api/v3";
string expectedArchiveLink = GetLinkToActionArchive(ApiUrl, ActionName, "master");
string archiveFile = await CreateRepoArchive();
using var stream = File.OpenRead(archiveFile);
var mockClientHandler = new Mock<HttpClientHandler>();
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(expectedArchiveLink)), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
_hc.SetSingleton(mockHandlerFactory.Object);
_ec.Setup(x => x.GetGitHubContext("api_url")).Returns(ApiUrl);
_configurationStore.Object.GetSettings().IsHostedServer = false; _configurationStore.Object.GetSettings().IsHostedServer = false;
var actionDirectory = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/no-such-action", "master");
Directory.CreateDirectory(actionDirectory);
var watermarkFile = $"{actionDirectory}.completed";
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
var actionFile = Path.Combine(actionDirectory, "action.yml");
File.WriteAllText(actionFile, @"
name: ""no-such-action""
runs:
using: node12
main: no-such-action.js
");
var testFile = Path.Combine(actionDirectory, "test-file");
File.WriteAllText(testFile, "asdf");
//Act // Act
await _actionManager.PrepareActionsAsync(_ec.Object, actions); await _actionManager.PrepareActionsAsync(_ec.Object, actions);
//Assert // Assert
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master.completed"); Assert.True(File.Exists(testFile));
Assert.True(File.Exists(watermarkFile));
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master", "action.yml");
Assert.True(File.Exists(actionYamlFile));
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void PrepareActions_DownloadCommunityActionFromGraph_OnPremises()
{
try
{
// Arrange
Setup();
const string ActionName = "ownerName/sample-action";
const string MungedActionName = "actions-community/ownerName-sample-action";
var actions = new List<Pipelines.ActionStep>
{
new Pipelines.ActionStep()
{
Name = "action",
Id = Guid.NewGuid(),
Reference = new Pipelines.RepositoryPathReference()
{
Name = ActionName,
Ref = "master",
RepositoryType = "GitHub"
}
}
};
// Return a valid action from GHES via mock
const string ApiUrl = "https://ghes.example.com/api/v3";
string builtInArchiveLink = GetLinkToActionArchive(ApiUrl, ActionName, "master");
string mungedArchiveLink = GetLinkToActionArchive(ApiUrl, MungedActionName, "master");
string archiveFile = await CreateRepoArchive();
using var stream = File.OpenRead(archiveFile);
var mockClientHandler = new Mock<HttpClientHandler>();
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(builtInArchiveLink)), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(mungedArchiveLink)), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
_hc.SetSingleton(mockHandlerFactory.Object);
_ec.Setup(x => x.GetGitHubContext("api_url")).Returns(ApiUrl);
_configurationStore.Object.GetSettings().IsHostedServer = false;
//Act
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
//Assert
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master.completed");
Assert.True(File.Exists(watermarkFile));
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master", "action.yml");
Assert.True(File.Exists(actionYamlFile));
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void PrepareActions_DownloadUnknownActionFromGraph_OnPremises()
{
try
{
// Arrange
Setup();
const string ActionName = "ownerName/sample-action";
var actions = new List<Pipelines.ActionStep>
{
new Pipelines.ActionStep()
{
Name = "action",
Id = Guid.NewGuid(),
Reference = new Pipelines.RepositoryPathReference()
{
Name = ActionName,
Ref = "master",
RepositoryType = "GitHub"
}
}
};
// Return a valid action from GHES via mock
const string ApiUrl = "https://ghes.example.com/api/v3";
string archiveLink = GetLinkToActionArchive(ApiUrl, ActionName, "master");
string archiveFile = await CreateRepoArchive();
using var stream = File.OpenRead(archiveFile);
var mockClientHandler = new Mock<HttpClientHandler>();
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
_hc.SetSingleton(mockHandlerFactory.Object);
_ec.Setup(x => x.GetGitHubContext("api_url")).Returns(ApiUrl);
_configurationStore.Object.GetSettings().IsHostedServer = false;
//Act
Func<Task> action = async () => await _actionManager.PrepareActionsAsync(_ec.Object, actions);
//Assert
await Assert.ThrowsAsync<ActionNotFoundException>(action);
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master.completed");
Assert.False(File.Exists(watermarkFile));
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "master", "action.yml");
Assert.False(File.Exists(actionYamlFile));
} }
finally finally
{ {
@@ -992,7 +862,7 @@ namespace GitHub.Runner.Common.Tests.Worker
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1092,7 +962,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1191,7 +1061,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1259,7 +1129,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1341,7 +1211,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1440,7 +1310,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1538,7 +1408,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1606,7 +1476,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1677,7 +1547,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'GitHub' author: 'GitHub'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1777,7 +1647,7 @@ runs:
name: 'Hello World' name: 'Hello World'
description: 'Greet the world and record the time' description: 'Greet the world and record the time'
author: 'Test Corporation' author: 'Test Corporation'
inputs: inputs:
greeting: # id of input greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true required: true
@@ -1867,82 +1737,6 @@ runs:
}; };
} }
/// <summary>
/// Creates a sample action in an archive on disk, similar to the archive
/// retrieved from GitHub's or GHES' repository API.
/// </summary>
/// <returns>The path on disk to the archive.</returns>
#if OS_WINDOWS
private Task<string> CreateRepoArchive()
#else
private async Task<string> CreateRepoArchive()
#endif
{
const string Content = @"
# Container action
name: 'Hello World'
description: 'Greet the world'
author: 'GitHub'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'task.js'
";
CreateAction(yamlContent: Content, instance: out _, directory: out string directory);
var tempDir = _hc.GetDirectory(WellKnownDirectory.Temp);
Directory.CreateDirectory(tempDir);
var archiveFile = Path.Combine(tempDir, Path.GetRandomFileName());
var trace = _hc.GetTrace();
#if OS_WINDOWS
ZipFile.CreateFromDirectory(directory, archiveFile, CompressionLevel.Fastest, includeBaseDirectory: true);
return Task.FromResult(archiveFile);
#else
string tar = WhichUtil.Which("tar", require: true, trace: trace);
// tar -xzf
using (var processInvoker = new ProcessInvokerWrapper())
{
processInvoker.Initialize(_hc);
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
trace.Info(args.Data);
}
});
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
trace.Error(args.Data);
}
});
string cwd = Path.GetDirectoryName(directory);
string inputDirectory = Path.GetFileName(directory);
int exitCode = await processInvoker.ExecuteAsync(_hc.GetDirectory(WellKnownDirectory.Bin), tar, $"-czf \"{archiveFile}\" -C \"{cwd}\" \"{inputDirectory}\"", null, CancellationToken.None);
if (exitCode != 0)
{
throw new NotSupportedException($"Can't use 'tar -czf' to create archive file: {archiveFile}. return code: {exitCode}.");
}
}
return archiveFile;
#endif
}
private static string GetLinkToActionArchive(string apiUrl, string repository, string @ref)
{
#if OS_WINDOWS
return $"{apiUrl}/repos/{repository}/zipball/{@ref}";
#else
return $"{apiUrl}/repos/{repository}/tarball/{@ref}";
#endif
}
private void Setup([CallerMemberName] string name = "") private void Setup([CallerMemberName] string name = "")
{ {
_ecTokenSource?.Dispose(); _ecTokenSource?.Dispose();
@@ -1978,7 +1772,6 @@ runs:
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object); _hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object); _hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
_hc.SetSingleton<IActionManifestManager>(actionManifest); _hc.SetSingleton<IActionManifestManager>(actionManifest);
_hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
_configurationStore = new Mock<IConfigurationStore>(); _configurationStore = new Mock<IConfigurationStore>();
_configurationStore _configurationStore