mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
15 Commits
users/tihu
...
v2.267.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3c9de49d | ||
|
|
0a27339b04 | ||
|
|
c776861f07 | ||
|
|
7cef9a27ca | ||
|
|
df7e16954e | ||
|
|
4e7d27a53c | ||
|
|
89d1418e48 | ||
|
|
e728b8594d | ||
|
|
de4490d06d | ||
|
|
2e800f857e | ||
|
|
312c7668a8 | ||
|
|
eaf39bb058 | ||
|
|
5815819f24 | ||
|
|
1aea046932 | ||
|
|
eda463601c |
@@ -23,7 +23,7 @@ An ADR is an Architectural Decision Record. This allows consensus on the direct
|
||||
|
||||
### Required Dev Dependencies
|
||||
|
||||
 Git for Windows [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
||||
  Git for Windows and Linux [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
||||
|
||||
### To Build, Test, Layout
|
||||
|
||||
@@ -43,6 +43,7 @@ Sample developer flow:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/actions/runner
|
||||
cd runner
|
||||
cd ./src
|
||||
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
||||
<make code changes>
|
||||
@@ -53,7 +54,7 @@ cd ./src
|
||||
### Editors
|
||||
|
||||
[Using Visual Studio Code](https://code.visualstudio.com/)
|
||||
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
|
||||
[Using Visual Studio](https://code.visualstudio.com/docs)
|
||||
|
||||
### Styling
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
## Features
|
||||
- N/A
|
||||
- Resolve action download info from server (#508, #515, #550)
|
||||
- Print runner and machine name to log. (#539)
|
||||
## Bugs
|
||||
- Handle `jq` returns "null" if the field does not exist in create-latest-svc.sh (#478)
|
||||
- Switch GITHUB_URL to GITHUB_SERVER_URL (#482)
|
||||
- Fix problem matcher for GHES (#488)
|
||||
- Fix container action inputs validation warning (#490)
|
||||
- Fix post step display name (#490)
|
||||
- Fix worker crash due to exception from evaluating step.env (#490)
|
||||
- Reduce input validation warnings (#506)
|
||||
- Fix null ref exception in SecretMasker caused by `hashfiles` timeout. (#516)
|
||||
- Add libicu66 to `./installDependencies.sh` for Ubuntu 20.04 (#535)
|
||||
- Fix DataContract with Token service (#532)
|
||||
- Skip search $PATH on command with fully qualified path (#526)
|
||||
- Restore SELinux context on service file when SELinux is enabled (#525)
|
||||
- Fix trailing '.0' for Int64 values in ContextData (#572)
|
||||
## Misc
|
||||
- N/A
|
||||
- Remove SPS/Token migration code. Remove GHES url manipulate code. (#513)
|
||||
- Add sub-step for developer flow for clarity (#523)
|
||||
- Update Links and Language to Git + VSCode (#522)
|
||||
- Update runner configuration exception message (#540)
|
||||
|
||||
## Windows x64
|
||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
2.267.1
|
||||
|
||||
@@ -70,8 +70,8 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||
apt install -y libicu63 || apt install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||
# libicu version prefer: libicu66 -> libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||
apt install -y libicu66 || apt install -y libicu63 || apt install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "'apt' failed with exit code '$?'"
|
||||
@@ -99,8 +99,8 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||
apt-get install -y libicu63 || apt-get install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||
# libicu version prefer: libicu66 -> libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||
apt-get install -y libicu66 || apt-get install -y libicu63 || apt-get install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "'apt-get' failed with exit code '$?'"
|
||||
|
||||
@@ -63,12 +63,25 @@ function install()
|
||||
|
||||
sed "s/{{User}}/${run_as_user}/g; s/{{Description}}/$(echo ${SVC_DESCRIPTION} | sed -e 's/[\/&]/\\&/g')/g; s/{{RunnerRoot}}/$(echo ${RUNNER_ROOT} | sed -e 's/[\/&]/\\&/g')/g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
|
||||
mv "${TEMP_PATH}" "${UNIT_PATH}" || failed "failed to copy unit file"
|
||||
|
||||
# Recent Fedora based Linux (CentOS/Redhat) has SELinux enabled by default
|
||||
# We need to restore security context on the unit file we added otherwise SystemD have no access to it.
|
||||
command -v getenforce > /dev/null
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
selinuxEnabled=$(getenforce)
|
||||
if [[ $selinuxEnabled == "Enforcing" ]]
|
||||
then
|
||||
# SELinux is enabled, we will need to Restore SELinux Context for the service file
|
||||
restorecon -r -v "${UNIT_PATH}" || failed "failed to restore SELinux context on ${UNIT_PATH}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# unit file should not be executable and world writable
|
||||
chmod 664 ${UNIT_PATH} || failed "failed to set permissions on ${UNIT_PATH}"
|
||||
chmod 664 "${UNIT_PATH}" || failed "failed to set permissions on ${UNIT_PATH}"
|
||||
systemctl daemon-reload || failed "failed to reload daemons"
|
||||
|
||||
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.
|
||||
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.
|
||||
cp ./bin/runsvc.sh ./runsvc.sh || failed "failed to copy runsvc.sh"
|
||||
chown ${run_as_uid}:${run_as_gid} ./runsvc.sh || failed "failed to set owner for runsvc.sh"
|
||||
chmod 755 ./runsvc.sh || failed "failed to set permission for runsvc.sh"
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace GitHub.Runner.Common
|
||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
||||
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||
Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed class JobServer : RunnerService, IJobServer
|
||||
@@ -113,5 +114,14 @@ namespace GitHub.Runner.Common
|
||||
CheckConnection();
|
||||
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// Action download info
|
||||
//-----------------------------------------------------------------
|
||||
public Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return _taskClient.ResolveActionDownloadInfoAsync(scopeIdentifier, hubName, planId, actions, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
else if (command.Unattended)
|
||||
{
|
||||
// if not replace and it is unattended config.
|
||||
throw new TaskAgentExistsException($"Pool {runnerSettings.PoolId} already contains a runner with name {runnerSettings.AgentName}.");
|
||||
throw new TaskAgentExistsException($"A runner exists with the same name {runnerSettings.AgentName}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -346,14 +346,14 @@ namespace GitHub.Runner.Sdk
|
||||
// data buffers one last time before returning
|
||||
ProcessOutput();
|
||||
|
||||
Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}.");
|
||||
}
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Ensure cancellation also finish on the cancellationToken.Register thread.
|
||||
await cancellationFinished.Task;
|
||||
Trace.Info($"Process Cancellation finished.");
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Ensure cancellation also finish on the cancellationToken.Register thread.
|
||||
await cancellationFinished.Task;
|
||||
Trace.Info($"Process Cancellation finished.");
|
||||
Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}.");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -11,6 +11,11 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which: '{command}'");
|
||||
if (Path.IsPathFullyQualified(command) && File.Exists(command))
|
||||
{
|
||||
trace?.Info($"Fully qualified path: '{command}'");
|
||||
return command;
|
||||
}
|
||||
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Services.Common;
|
||||
using WebApi = GitHub.DistributedTask.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||
|
||||
@@ -545,124 +546,77 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation is temporary and will be removed when we switch to a REST API call to the service to resolve the download info
|
||||
private async Task<bool> RepoExistsAsync(IExecutionContext executionContext, Pipelines.RepositoryPathReference repositoryReference, string authorization)
|
||||
// This implementation is temporary and will be replaced with a REST API call to the service to resolve
|
||||
private async Task<IDictionary<string, WebApi.ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
|
||||
{
|
||||
var apiUrl = GetApiUrl(executionContext);
|
||||
var repoUrl = $"{apiUrl}/repos/{repositoryReference.Name}";
|
||||
executionContext.Output("Getting action download info");
|
||||
|
||||
// Convert to action reference
|
||||
var actionReferences = actions
|
||||
.GroupBy(x => GetDownloadInfoLookupKey(x))
|
||||
.Where(x => !string.IsNullOrEmpty(x.Key))
|
||||
.Select(x =>
|
||||
{
|
||||
var action = x.First();
|
||||
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
|
||||
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
||||
return new WebApi.ActionReference
|
||||
{
|
||||
NameWithOwner = repositoryReference.Name,
|
||||
Ref = repositoryReference.Ref,
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Nothing to resolve?
|
||||
if (actionReferences.Count == 0)
|
||||
{
|
||||
return new Dictionary<string, WebApi.ActionDownloadInfo>();
|
||||
}
|
||||
|
||||
// Resolve download info
|
||||
var jobServer = HostContext.GetService<IJobServer>();
|
||||
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
|
||||
for (var attempt = 1; attempt <= 3; attempt++)
|
||||
{
|
||||
executionContext.Debug($"Checking whether repo exists: {repoUrl}");
|
||||
try
|
||||
{
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(authorization);
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
using (var response = await httpClient.GetAsync(repoUrl))
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Throw
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Plan.ScopeIdentifier, executionContext.Plan.PlanType, executionContext.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception ex) when (attempt < 3)
|
||||
{
|
||||
if (attempt < 3)
|
||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||
executionContext.Debug(ex.ToString());
|
||||
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||
{
|
||||
executionContext.Debug($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionContext.Error($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}");
|
||||
throw;
|
||||
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
||||
await Task.Delay(backoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Never reaches here
|
||||
}
|
||||
|
||||
// This implementation is temporary and will be replaced with a REST API call to the service to resolve
|
||||
private async Task<Dictionary<string, ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
|
||||
{
|
||||
var result = new Dictionary<string, ActionDownloadInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
||||
ArgUtil.NotNull(actionDownloadInfos.Actions, nameof(actionDownloadInfos.Actions));
|
||||
var apiUrl = GetApiUrl(executionContext);
|
||||
var defaultAccessToken = executionContext.GetGitHubContext("token");
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
var runnerSettings = configurationStore.GetSettings();
|
||||
var apiUrl = GetApiUrl(executionContext);
|
||||
var accessToken = executionContext.GetGitHubContext("token");
|
||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
|
||||
var authorization = $"Basic {base64EncodingToken}";
|
||||
|
||||
foreach (var action in actions)
|
||||
foreach (var actionDownloadInfo in actionDownloadInfos.Actions.Values)
|
||||
{
|
||||
var lookupKey = GetDownloadInfoLookupKey(action);
|
||||
if (string.IsNullOrEmpty(lookupKey) || result.ContainsKey(lookupKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Add secret
|
||||
HostContext.SecretMasker.AddValue(actionDownloadInfo.Authentication?.Token);
|
||||
|
||||
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
|
||||
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
||||
|
||||
var downloadInfo = default(ActionDownloadInfo);
|
||||
|
||||
if (runnerSettings.IsHostedServer)
|
||||
// Default auth token
|
||||
if (string.IsNullOrEmpty(actionDownloadInfo.Authentication?.Token))
|
||||
{
|
||||
downloadInfo = new ActionDownloadInfo
|
||||
{
|
||||
NameWithOwner = repositoryReference.Name,
|
||||
Ref = repositoryReference.Ref,
|
||||
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
|
||||
Authorization = authorization,
|
||||
};
|
||||
actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
|
||||
}
|
||||
// Test whether the repo exists in the instance
|
||||
else if (await RepoExistsAsync(executionContext, repositoryReference, authorization))
|
||||
{
|
||||
downloadInfo = new ActionDownloadInfo
|
||||
{
|
||||
NameWithOwner = repositoryReference.Name,
|
||||
Ref = repositoryReference.Ref,
|
||||
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
|
||||
Authorization = authorization,
|
||||
};
|
||||
}
|
||||
// Fallback to dotcom
|
||||
else
|
||||
{
|
||||
downloadInfo = new ActionDownloadInfo
|
||||
{
|
||||
NameWithOwner = repositoryReference.Name,
|
||||
Ref = repositoryReference.Ref,
|
||||
ArchiveLink = BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
|
||||
Authorization = null,
|
||||
};
|
||||
}
|
||||
|
||||
result.Add(lookupKey, downloadInfo);
|
||||
}
|
||||
|
||||
// Register secrets
|
||||
foreach (var downloadInfo in result.Values)
|
||||
{
|
||||
HostContext.SecretMasker.AddValue(downloadInfo.Authorization);
|
||||
}
|
||||
|
||||
return result;
|
||||
return actionDownloadInfos.Actions;
|
||||
}
|
||||
|
||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||
@@ -709,7 +663,6 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
string apiUrl = GetApiUrl(executionContext);
|
||||
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
|
||||
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
|
||||
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
|
||||
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
|
||||
return;
|
||||
@@ -735,7 +688,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
foreach (var downloadAttempt in downloadAttempts)
|
||||
{
|
||||
Trace.Info($"Download archive '{downloadAttempt.ArchiveLink}' to '{destDirectory}'.");
|
||||
try
|
||||
{
|
||||
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
|
||||
@@ -751,7 +703,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadInfo downloadInfo)
|
||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
||||
{
|
||||
Trace.Entering();
|
||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||
@@ -774,7 +726,6 @@ namespace GitHub.Runner.Worker
|
||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
||||
}
|
||||
|
||||
Trace.Info($"Download archive '{downloadInfo.ArchiveLink}' to '{destDirectory}'.");
|
||||
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
|
||||
}
|
||||
|
||||
@@ -799,7 +750,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
|
||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, ActionDownloadInfo downloadInfo, string destDirectory)
|
||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
||||
{
|
||||
//download and extract action in a temp folder and rename it on success
|
||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||
@@ -807,11 +758,12 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
#if OS_WINDOWS
|
||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
|
||||
string link = downloadInfo?.ZipballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||
#else
|
||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
||||
string link = downloadInfo?.TarballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||
#endif
|
||||
|
||||
string link = downloadInfo != null ? downloadInfo.ArchiveLink : actionDownloadDetails.ArchiveLink;
|
||||
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||
try
|
||||
{
|
||||
@@ -839,7 +791,7 @@ namespace GitHub.Runner.Worker
|
||||
// FF DistributedTask.NewActionMetadata
|
||||
else
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authorization);
|
||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||
}
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
@@ -1137,20 +1089,23 @@ namespace GitHub.Runner.Worker
|
||||
return $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
||||
}
|
||||
|
||||
private static AuthenticationHeaderValue CreateAuthHeader(string authorization)
|
||||
private static string GetDownloadInfoLookupKey(WebApi.ActionDownloadInfo info)
|
||||
{
|
||||
if (string.IsNullOrEmpty(authorization))
|
||||
ArgUtil.NotNullOrEmpty(info.NameWithOwner, nameof(info.NameWithOwner));
|
||||
ArgUtil.NotNullOrEmpty(info.Ref, nameof(info.Ref));
|
||||
return $"{info.NameWithOwner}@{info.Ref}";
|
||||
}
|
||||
|
||||
private AuthenticationHeaderValue CreateAuthHeader(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var split = authorization.Split(new char[] { ' ' }, 2);
|
||||
if (split.Length != 2 || string.IsNullOrWhiteSpace(split[0]) || string.IsNullOrWhiteSpace(split[1]))
|
||||
{
|
||||
throw new Exception("Unexpected authorization header format");
|
||||
}
|
||||
|
||||
return new AuthenticationHeaderValue(split[0].Trim(), split[1].Trim());
|
||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{token}"));
|
||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||
}
|
||||
|
||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||
@@ -1166,17 +1121,6 @@ namespace GitHub.Runner.Worker
|
||||
ConfigureAuthorization = configureAuthorization;
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionDownloadInfo
|
||||
{
|
||||
public string NameWithOwner { get; set; }
|
||||
|
||||
public string Ref { get; set; }
|
||||
|
||||
public string ArchiveLink { get; set; }
|
||||
|
||||
public string Authorization { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Definition
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace GitHub.Runner.Worker
|
||||
TaskResult? CommandResult { get; set; }
|
||||
CancellationToken CancellationToken { get; }
|
||||
List<ServiceEndpoint> Endpoints { get; }
|
||||
TaskOrchestrationPlanReference Plan { get; }
|
||||
|
||||
PlanFeatures Features { get; }
|
||||
Variables Variables { get; }
|
||||
@@ -141,6 +142,7 @@ namespace GitHub.Runner.Worker
|
||||
public Task ForceCompleted => _forceCompleted.Task;
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||
public TaskOrchestrationPlanReference Plan { get; private set; }
|
||||
public Variables Variables { get; private set; }
|
||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
||||
@@ -275,6 +277,7 @@ namespace GitHub.Runner.Worker
|
||||
child.Features = Features;
|
||||
child.Variables = Variables;
|
||||
child.Endpoints = Endpoints;
|
||||
child.Plan = Plan;
|
||||
if (intraActionState == null)
|
||||
{
|
||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -576,7 +579,8 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||
|
||||
// Features
|
||||
// Plan
|
||||
Plan = message.Plan;
|
||||
Features = PlanUtil.GetFeatures(message.Plan);
|
||||
|
||||
// Endpoints
|
||||
|
||||
@@ -64,6 +64,20 @@ namespace GitHub.Runner.Worker
|
||||
context.Debug($"Starting: Set up job");
|
||||
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
||||
|
||||
var setting = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
var credFile = HostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
if (File.Exists(credFile))
|
||||
{
|
||||
var credData = IOUtil.LoadObject<CredentialData>(credFile);
|
||||
if (credData != null &&
|
||||
credData.Data.TryGetValue("clientId", out var clientId))
|
||||
{
|
||||
// print out HostName for self-hosted runner
|
||||
context.Output($"Runner name: '{setting.AgentName}'");
|
||||
context.Output($"Machine name: '{Environment.MachineName}'");
|
||||
}
|
||||
}
|
||||
|
||||
var setupInfoFile = HostContext.GetConfigFile(WellKnownConfigFile.SetupInfo);
|
||||
if (File.Exists(setupInfoFile))
|
||||
{
|
||||
|
||||
@@ -317,5 +317,37 @@ namespace GitHub.DistributedTask.WebApi
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Preview API] Resolves information required to download actions (URL, token) defined in an orchestration.
|
||||
/// </summary>
|
||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
||||
/// <param name="planId"></param>
|
||||
/// <param name="actionReferenceList"></param>
|
||||
/// <param name="userState"></param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
public virtual Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(
|
||||
Guid scopeIdentifier,
|
||||
string hubName,
|
||||
Guid planId,
|
||||
ActionReferenceList actionReferenceList,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
Guid locationId = new Guid("27d7f831-88c1-4719-8ca1-6a061dad90eb");
|
||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
||||
HttpContent content = new ObjectContent<ActionReferenceList>(actionReferenceList, new VssJsonMediaTypeFormatter(true));
|
||||
|
||||
return SendAsync<ActionDownloadInfoCollection>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken,
|
||||
content: content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,12 @@ namespace GitHub.DistributedTask.Pipelines.ContextData
|
||||
var floored = Math.Floor(m_value);
|
||||
if (m_value == floored && m_value <= (Double)Int32.MaxValue && m_value >= (Double)Int32.MinValue)
|
||||
{
|
||||
Int32 flooredInt = (Int32)floored;
|
||||
var flooredInt = (Int32)floored;
|
||||
return (JToken)flooredInt;
|
||||
}
|
||||
else if (m_value == floored && m_value <= (Double)Int64.MaxValue && m_value >= (Double)Int64.MinValue)
|
||||
{
|
||||
var flooredInt = (Int64)floored;
|
||||
return (JToken)flooredInt;
|
||||
}
|
||||
else
|
||||
|
||||
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal file
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class ActionDownloadInfo
|
||||
{
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public ActionDownloadAuthentication Authentication { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string NameWithOwner { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string ResolvedNameWithOwner { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string ResolvedSha { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string TarballUrl { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string Ref { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string ZipballUrl { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadAuthentication
|
||||
{
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class ActionDownloadInfoCollection
|
||||
{
|
||||
[DataMember]
|
||||
public IDictionary<string, ActionDownloadInfo> Actions
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal file
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class ActionReference
|
||||
{
|
||||
[DataMember]
|
||||
public string NameWithOwner
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public string Ref
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class ActionReferenceList
|
||||
{
|
||||
[DataMember]
|
||||
public IList<ActionReference> Actions
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace GitHub.Services.OAuth
|
||||
/// <summary>
|
||||
/// Gets or sets the error description for the response.
|
||||
/// </summary>
|
||||
[DataMember(Name = "errordescription", EmitDefaultValue = false)]
|
||||
[DataMember(Name = "error_description", EmitDefaultValue = false)]
|
||||
public String ErrorDescription
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -70,5 +70,24 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void WhichHandleFullyQualifiedPath()
|
||||
{
|
||||
using (TestHostContext hc = new TestHostContext(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
var gitPath = WhichUtil.Which("git", require: true, trace: trace);
|
||||
var gitPath2 = WhichUtil.Which(gitPath, require: true, trace: trace);
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(gitPath, gitPath2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
private Mock<IConfigurationStore> _configurationStore;
|
||||
private Mock<IDockerCommandManager> _dockerManager;
|
||||
private Mock<IExecutionContext> _ec;
|
||||
private Mock<IJobServer> _jobServer;
|
||||
private Mock<IRunnerPluginManager> _pluginManager;
|
||||
private TestHostContext _hc;
|
||||
private ActionManager _actionManager;
|
||||
@@ -3583,6 +3584,7 @@ runs:
|
||||
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, variables));
|
||||
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||
_ec.Setup(x => x.Plan).Returns(new TaskOrchestrationPlanReference());
|
||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||
@@ -3593,6 +3595,25 @@ runs:
|
||||
|
||||
_dockerManager.Setup(x => x.DockerBuild(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(0));
|
||||
|
||||
_jobServer = new Mock<IJobServer>();
|
||||
_jobServer.Setup(x => x.ResolveActionDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>()))
|
||||
.Returns((Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
|
||||
foreach (var action in actions.Actions)
|
||||
{
|
||||
var key = $"{action.NameWithOwner}@{action.Ref}";
|
||||
result.Actions[key] = new ActionDownloadInfo
|
||||
{
|
||||
NameWithOwner = action.NameWithOwner,
|
||||
Ref = action.Ref,
|
||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
||||
};
|
||||
}
|
||||
return Task.FromResult(result);
|
||||
});
|
||||
|
||||
_pluginManager = new Mock<IRunnerPluginManager>();
|
||||
_pluginManager.Setup(x => x.GetPluginAction(It.IsAny<string>())).Returns(new RunnerPluginActionInfo() { PluginTypeName = "plugin.class, plugin", PostPluginTypeName = "plugin.cleanup, plugin" });
|
||||
|
||||
@@ -3600,6 +3621,7 @@ runs:
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
||||
_hc.SetSingleton<IJobServer>(_jobServer.Object);
|
||||
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
|
||||
_hc.SetSingleton<IActionManifestManager>(actionManifest);
|
||||
_hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||
|
||||
51
src/dev.sh
51
src/dev.sh
@@ -173,57 +173,6 @@ function package ()
|
||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${zip_name}\")"
|
||||
fi
|
||||
|
||||
runner_patch_pkg_name="actions-runner-${RUNTIME_ID}-${runner_ver}-patch"
|
||||
|
||||
heading "Packaging Patch Version ${runner_patch_pkg_name}"
|
||||
|
||||
echo "Downloading latest runner ..."
|
||||
|
||||
mkdir -p "_temp"
|
||||
pushd "_temp" > /dev/null
|
||||
|
||||
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
|
||||
latest_version=$(echo ${latest_version_label:1})
|
||||
latest_version_runner_file=""
|
||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
||||
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.tar.gz"
|
||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
||||
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.zip"
|
||||
fi
|
||||
|
||||
latest_version_download_url="https://github.com/actions/runner/releases/download/${latest_version_label}/${latest_version_runner_file}"
|
||||
|
||||
echo "Downloading ${latest_version_label} for ${RUNTIME_ID} ..."
|
||||
echo $latest_version_download_url
|
||||
|
||||
curl -O -L ${latest_version_download_url}
|
||||
|
||||
mkdir -p "latest_runner"
|
||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
||||
tar xzf "./${latest_version_runner_file}" -C latest_runner
|
||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::ExtractToDirectory(\"./${latest_version_runner_file}\", \"latest_runner\")"
|
||||
fi
|
||||
|
||||
mkdir -p "_patch"
|
||||
diff -qrN "${LAYOUT_DIR}" "latest_runner" | cut -d " " -f 2 | xargs -t -I file cp file "_patch/"$(echo file | cut -c ${#LAYOUT_DIR}- | cut -c 3-)
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
||||
patch_tar_name="${runner_patch_pkg_name}.tar.gz"
|
||||
echo "Creating $patch_tar_name in ${PACKAGE_DIR}/_temp/_patch"
|
||||
tar -czf "${patch_tar_name}" -C "${PACKAGE_DIR}/_temp/_patch" .
|
||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
||||
patch_zip_name="${runner_patch_pkg_name}.zip"
|
||||
echo "Convert ${PACKAGE_DIR} to Windows style path"
|
||||
window_path=${PACKAGE_DIR:1}
|
||||
window_path=${window_path:0:1}:${window_path:1}
|
||||
echo "Creating $patch_zip_name in ${window_path}/_temp/_patch"
|
||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${patch_zip_name}\")"
|
||||
fi
|
||||
|
||||
rm -Rf "${PACKAGE_DIR}/_temp"
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.263.0
|
||||
2.267.1
|
||||
|
||||
Reference in New Issue
Block a user