mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
Add job log upload support (#2447)
* Refactor and add job log upload support * Rename method to be consistent
This commit is contained in:
@@ -32,6 +32,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
||||||
Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken);
|
Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken);
|
||||||
Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
||||||
|
Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
||||||
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
|
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
|
||||||
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||||
@@ -335,6 +336,14 @@ namespace GitHub.Runner.Common
|
|||||||
throw new InvalidOperationException("Results client is not initialized.");
|
throw new InvalidOperationException("Results client is not initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_resultsClient != null)
|
||||||
|
{
|
||||||
|
return _resultsClient.UploadResultsJobLogAsync(planId, jobId, file, finalize, firstBlock, lineCount, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Results client is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ namespace GitHub.Runner.Common
|
|||||||
private bool _firstConsoleOutputs = true;
|
private bool _firstConsoleOutputs = true;
|
||||||
|
|
||||||
private bool _resultsClientInitiated = false;
|
private bool _resultsClientInitiated = false;
|
||||||
|
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -252,12 +253,6 @@ namespace GitHub.Runner.Common
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timelineRecordId == _jobTimelineRecordId && String.Equals(type, CoreAttachmentType.ResultsLog, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
Trace.Verbose("Skipping job log {0} for record {1}", path, timelineRecordId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// all parameter not null, file path exist.
|
// all parameter not null, file path exist.
|
||||||
var newFile = new ResultsUploadFileInfo()
|
var newFile = new ResultsUploadFileInfo()
|
||||||
{
|
{
|
||||||
@@ -503,8 +498,16 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase))
|
else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Trace.Info($"Got a step log file to send to results service.");
|
if (file.RecordId != _jobTimelineRecordId)
|
||||||
await UploadResultsStepLogFile(file);
|
{
|
||||||
|
Trace.Info($"Got a step log file to send to results service.");
|
||||||
|
await UploadResultsStepLogFile(file);
|
||||||
|
}
|
||||||
|
else if (file.RecordId == _jobTimelineRecordId)
|
||||||
|
{
|
||||||
|
Trace.Info($"Got a job log file to send to results service.");
|
||||||
|
await UploadResultsJobLogFile(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -813,40 +816,43 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
private async Task UploadSummaryFile(ResultsUploadFileInfo file)
|
private async Task UploadSummaryFile(ResultsUploadFileInfo file)
|
||||||
{
|
{
|
||||||
bool uploadSucceed = false;
|
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
|
||||||
try
|
ResultsFileUploadHandler summaryHandler = async (file) =>
|
||||||
{
|
{
|
||||||
// Upload the step summary
|
|
||||||
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
|
|
||||||
await _jobServer.CreateStepSummaryAsync(file.PlanId, file.JobId, file.RecordId, file.Path, CancellationToken.None);
|
await _jobServer.CreateStepSummaryAsync(file.PlanId, file.JobId, file.RecordId, file.Path, CancellationToken.None);
|
||||||
|
};
|
||||||
|
|
||||||
uploadSucceed = true;
|
await UploadResultsFile(file, summaryHandler);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (uploadSucceed && file.DeleteSource)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(file.Path);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Catch exception during delete success results uploaded summary file.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file)
|
private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file)
|
||||||
|
{
|
||||||
|
Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}");
|
||||||
|
ResultsFileUploadHandler stepLogHandler = async (file) =>
|
||||||
|
{
|
||||||
|
await _jobServer.CreateResultsStepLogAsync(file.PlanId, file.JobId, file.RecordId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
||||||
|
};
|
||||||
|
|
||||||
|
await UploadResultsFile(file, stepLogHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UploadResultsJobLogFile(ResultsUploadFileInfo file)
|
||||||
|
{
|
||||||
|
Trace.Info($"Starting upload of job log file to results service {file.Name}, {file.Path}");
|
||||||
|
ResultsFileUploadHandler jobLogHandler = async (file) =>
|
||||||
|
{
|
||||||
|
await _jobServer.CreateResultsJobLogAsync(file.PlanId, file.JobId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
||||||
|
};
|
||||||
|
|
||||||
|
await UploadResultsFile(file, jobLogHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UploadResultsFile(ResultsUploadFileInfo file, ResultsFileUploadHandler uploadHandler)
|
||||||
{
|
{
|
||||||
bool uploadSucceed = false;
|
bool uploadSucceed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}");
|
await uploadHandler(file);
|
||||||
await _jobServer.CreateResultsStepLogAsync(file.PlanId, file.JobId, file.RecordId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
|
||||||
|
|
||||||
uploadSucceed = true;
|
uploadSucceed = true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -28,6 +28,42 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
public string BlobStorageType;
|
public string BlobStorageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
|
public class StepSummaryMetadataCreate
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string StepBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowRunBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowJobRunBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public long Size;
|
||||||
|
[DataMember]
|
||||||
|
public string UploadedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
|
public class GetSignedJobLogsURLRequest
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowJobRunBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowRunBackendId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
|
public class GetSignedJobLogsURLResponse
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string LogsUrl;
|
||||||
|
[DataMember]
|
||||||
|
public string BlobStorageType;
|
||||||
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
public class GetSignedStepLogsURLRequest
|
public class GetSignedStepLogsURLRequest
|
||||||
@@ -47,41 +83,15 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
[DataMember]
|
[DataMember]
|
||||||
public string LogsUrl;
|
public string LogsUrl;
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public long SoftSizeLimit;
|
|
||||||
[DataMember]
|
|
||||||
public string BlobStorageType;
|
public string BlobStorageType;
|
||||||
|
[DataMember]
|
||||||
|
public long SoftSizeLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
public class StepSummaryMetadataCreate
|
public class JobLogsMetadataCreate
|
||||||
{
|
{
|
||||||
[DataMember]
|
|
||||||
public string StepBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowJobRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public long Size;
|
|
||||||
[DataMember]
|
|
||||||
public string UploadedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class CreateStepSummaryMetadataResponse
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public bool Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class StepLogsMetadataCreate
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string StepBackendId;
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public string WorkflowRunBackendId;
|
public string WorkflowRunBackendId;
|
||||||
[DataMember]
|
[DataMember]
|
||||||
@@ -94,7 +104,23 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
public class CreateStepLogsMetadataResponse
|
public class StepLogsMetadataCreate
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowRunBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string WorkflowJobRunBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string StepBackendId;
|
||||||
|
[DataMember]
|
||||||
|
public string UploadedAt;
|
||||||
|
[DataMember]
|
||||||
|
public long LineCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
|
public class CreateMetadataResponse
|
||||||
{
|
{
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public bool Ok;
|
public bool Ok;
|
||||||
|
|||||||
@@ -24,7 +24,26 @@ namespace GitHub.Services.Results.Client
|
|||||||
m_formatter = new JsonMediaTypeFormatter();
|
m_formatter = new JsonMediaTypeFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GetSignedStepSummaryURLResponse> GetStepSummaryUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
// Get Sas URL calls
|
||||||
|
private async Task<T> GetResultsSignedURLResponse<R, T>(Uri uri, CancellationToken cancellationToken, R request)
|
||||||
|
{
|
||||||
|
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
||||||
|
{
|
||||||
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
||||||
|
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
|
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
||||||
|
{
|
||||||
|
requestMessage.Content = content;
|
||||||
|
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
||||||
|
{
|
||||||
|
return await ReadJsonContentAsync<T>(response, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GetSignedStepSummaryURLResponse> GetStepSummaryUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new GetSignedStepSummaryURLRequest()
|
var request = new GetSignedStepSummaryURLRequest()
|
||||||
{
|
{
|
||||||
@@ -33,25 +52,12 @@ namespace GitHub.Services.Results.Client
|
|||||||
StepBackendId = stepId.ToString()
|
StepBackendId = stepId.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
var stepSummaryUploadRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/GetStepSummarySignedBlobURL");
|
var getStepSummarySignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetStepSummarySignedBlobURL);
|
||||||
|
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadRequest))
|
return await GetResultsSignedURLResponse<GetSignedStepSummaryURLRequest, GetSignedStepSummaryURLResponse>(getStepSummarySignedBlobURLEndpoint, cancellationToken, request);
|
||||||
{
|
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<GetSignedStepSummaryURLRequest>(request, m_formatter))
|
|
||||||
{
|
|
||||||
requestMessage.Content = content;
|
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
|
||||||
{
|
|
||||||
return await ReadJsonContentAsync<GetSignedStepSummaryURLResponse>(response, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GetSignedStepLogsURLResponse> GetStepLogUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
private async Task<GetSignedStepLogsURLResponse> GetStepLogUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new GetSignedStepLogsURLRequest()
|
var request = new GetSignedStepLogsURLRequest()
|
||||||
{
|
{
|
||||||
@@ -60,19 +66,43 @@ namespace GitHub.Services.Results.Client
|
|||||||
StepBackendId = stepId.ToString(),
|
StepBackendId = stepId.ToString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var stepLogsUploadRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/GetStepLogsSignedBlobURL");
|
var getStepLogsSignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetStepLogsSignedBlobURL);
|
||||||
|
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepLogsUploadRequest))
|
return await GetResultsSignedURLResponse<GetSignedStepLogsURLRequest, GetSignedStepLogsURLResponse>(getStepLogsSignedBlobURLEndpoint, cancellationToken, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GetSignedJobLogsURLResponse> GetJobLogUploadUrlAsync(string planId, string jobId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var request = new GetSignedJobLogsURLRequest()
|
||||||
|
{
|
||||||
|
WorkflowJobRunBackendId = jobId,
|
||||||
|
WorkflowRunBackendId = planId,
|
||||||
|
};
|
||||||
|
|
||||||
|
var getJobLogsSignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetJobLogsSignedBlobURL);
|
||||||
|
|
||||||
|
return await GetResultsSignedURLResponse<GetSignedJobLogsURLRequest, GetSignedJobLogsURLResponse>(getJobLogsSignedBlobURLEndpoint, cancellationToken, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create metadata calls
|
||||||
|
|
||||||
|
private async Task CreateMetadata<R>(Uri uri, CancellationToken cancellationToken, R request, string timestamp)
|
||||||
|
{
|
||||||
|
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
||||||
{
|
{
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<GetSignedStepLogsURLRequest>(request, m_formatter))
|
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
||||||
{
|
{
|
||||||
requestMessage.Content = content;
|
requestMessage.Content = content;
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
||||||
{
|
{
|
||||||
return await ReadJsonContentAsync<GetSignedStepLogsURLResponse>(response, cancellationToken);
|
var jsonResponse = await ReadJsonContentAsync<CreateMetadataResponse>(response, cancellationToken);
|
||||||
|
if (!jsonResponse.Ok)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to mark {typeof(R).Name} upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, timestamp: {timestamp}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +110,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
|
|
||||||
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, Guid stepId, long size, CancellationToken cancellationToken)
|
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, Guid stepId, long size, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
|
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||||
var request = new StepSummaryMetadataCreate()
|
var request = new StepSummaryMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
WorkflowJobRunBackendId = jobId,
|
||||||
@@ -90,31 +120,13 @@ namespace GitHub.Services.Results.Client
|
|||||||
UploadedAt = timestamp
|
UploadedAt = timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
var stepSummaryUploadCompleteRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/CreateStepSummaryMetadata");
|
var createStepSummaryMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateStepSummaryMetadata);
|
||||||
|
await CreateMetadata<StepSummaryMetadataCreate>(createStepSummaryMetadataEndpoint, cancellationToken, request, timestamp);
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadCompleteRequest))
|
|
||||||
{
|
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<StepSummaryMetadataCreate>(request, m_formatter))
|
|
||||||
{
|
|
||||||
requestMessage.Content = content;
|
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
|
||||||
{
|
|
||||||
var jsonResponse = await ReadJsonContentAsync<CreateStepSummaryMetadataResponse>(response, cancellationToken);
|
|
||||||
if (!jsonResponse.Ok)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to mark step summary upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, size: {size}, timestamp: {timestamp}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StepLogUploadCompleteAsync(string planId, string jobId, Guid stepId, long lineCount, CancellationToken cancellationToken)
|
private async Task StepLogUploadCompleteAsync(string planId, string jobId, Guid stepId, long lineCount, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
|
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||||
var request = new StepLogsMetadataCreate()
|
var request = new StepLogsMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
WorkflowJobRunBackendId = jobId,
|
||||||
@@ -124,29 +136,26 @@ namespace GitHub.Services.Results.Client
|
|||||||
LineCount = lineCount,
|
LineCount = lineCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
var stepLogsUploadCompleteRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/CreateStepLogsMetadata");
|
var createStepLogsMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateStepLogsMetadata);
|
||||||
|
await CreateMetadata<StepLogsMetadataCreate>(createStepLogsMetadataEndpoint, cancellationToken, request, timestamp);
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepLogsUploadCompleteRequest))
|
|
||||||
{
|
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<StepLogsMetadataCreate>(request, m_formatter))
|
|
||||||
{
|
|
||||||
requestMessage.Content = content;
|
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
|
||||||
{
|
|
||||||
var jsonResponse = await ReadJsonContentAsync<CreateStepSummaryMetadataResponse>(response, cancellationToken);
|
|
||||||
if (!jsonResponse.Ok)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to mark step log upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, timestamp: {timestamp}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> UploadFileAsync(string url, string blobStorageType, FileStream file, CancellationToken cancellationToken)
|
private async Task JobLogUploadCompleteAsync(string planId, string jobId, long lineCount, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||||
|
var request = new JobLogsMetadataCreate()
|
||||||
|
{
|
||||||
|
WorkflowJobRunBackendId = jobId,
|
||||||
|
WorkflowRunBackendId = planId,
|
||||||
|
UploadedAt = timestamp,
|
||||||
|
LineCount = lineCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
var createJobLogsMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateJobLogsMetadata);
|
||||||
|
await CreateMetadata<JobLogsMetadataCreate>(createJobLogsMetadataEndpoint, cancellationToken, request, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> UploadBlockFileAsync(string url, string blobStorageType, FileStream file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Upload the file to the url
|
// Upload the file to the url
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
||||||
@@ -156,7 +165,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
|
|
||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
||||||
{
|
{
|
||||||
request.Content.Headers.Add("x-ms-blob-type", "BlockBlob");
|
request.Content.Headers.Add(Constants.AzureBlobTypeHeader, Constants.AzureBlockBlob);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
||||||
@@ -177,7 +186,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
};
|
};
|
||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
||||||
{
|
{
|
||||||
request.Content.Headers.Add("x-ms-blob-type", "AppendBlob");
|
request.Content.Headers.Add(Constants.AzureBlobTypeHeader, Constants.AzureAppendBlob);
|
||||||
request.Content.Headers.Add("Content-Length", "0");
|
request.Content.Headers.Add("Content-Length", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +212,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
||||||
{
|
{
|
||||||
request.Content.Headers.Add("Content-Length", fileSize.ToString());
|
request.Content.Headers.Add("Content-Length", fileSize.ToString());
|
||||||
request.Content.Headers.Add("x-ms-blob-sealed", finalize.ToString());
|
request.Content.Headers.Add(Constants.AzureBlobSealedHeader, finalize.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
||||||
@@ -236,7 +245,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
// Upload the file
|
// Upload the file
|
||||||
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
{
|
{
|
||||||
var response = await UploadFileAsync(uploadUrlResponse.SummaryUrl, uploadUrlResponse.BlobStorageType, fileStream, cancellationToken);
|
var response = await UploadBlockFileAsync(uploadUrlResponse.SummaryUrl, uploadUrlResponse.BlobStorageType, fileStream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send step summary upload complete message
|
// Send step summary upload complete message
|
||||||
@@ -253,9 +262,6 @@ namespace GitHub.Services.Results.Client
|
|||||||
throw new Exception("Failed to get step log upload url");
|
throw new Exception("Failed to get step log upload url");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we want to throw an exception here or should we just be uploading/truncating the data
|
|
||||||
var fileSize = new FileInfo(file).Length;
|
|
||||||
|
|
||||||
// Create the Append blob
|
// Create the Append blob
|
||||||
if (firstBlock)
|
if (firstBlock)
|
||||||
{
|
{
|
||||||
@@ -263,6 +269,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upload content
|
// Upload content
|
||||||
|
var fileSize = new FileInfo(file).Length;
|
||||||
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
{
|
{
|
||||||
var response = await UploadAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
var response = await UploadAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
||||||
@@ -276,8 +283,59 @@ namespace GitHub.Services.Results.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle file upload for job log
|
||||||
|
public async Task UploadResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Get the upload url
|
||||||
|
var uploadUrlResponse = await GetJobLogUploadUrlAsync(planId, jobId, cancellationToken);
|
||||||
|
if (uploadUrlResponse == null || uploadUrlResponse.LogsUrl == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to get job log upload url");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Append blob
|
||||||
|
if (firstBlock)
|
||||||
|
{
|
||||||
|
await CreateAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload content
|
||||||
|
var fileSize = new FileInfo(file).Length;
|
||||||
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
|
{
|
||||||
|
var response = await UploadAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metadata
|
||||||
|
if (finalize)
|
||||||
|
{
|
||||||
|
// Send step log upload complete message
|
||||||
|
await JobLogUploadCompleteAsync(planId, jobId, lineCount, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MediaTypeFormatter m_formatter;
|
private MediaTypeFormatter m_formatter;
|
||||||
private Uri m_resultsServiceUrl;
|
private Uri m_resultsServiceUrl;
|
||||||
private string m_token;
|
private string m_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants specific to results
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public static readonly string TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fffK";
|
||||||
|
|
||||||
|
public static readonly string ResultsReceiverTwirpEndpoint = "twirp/results.services.receiver.Receiver/";
|
||||||
|
public static readonly string GetStepSummarySignedBlobURL = ResultsReceiverTwirpEndpoint + "GetStepSummarySignedBlobURL";
|
||||||
|
public static readonly string CreateStepSummaryMetadata = ResultsReceiverTwirpEndpoint + "CreateStepSummaryMetadata";
|
||||||
|
public static readonly string GetStepLogsSignedBlobURL = ResultsReceiverTwirpEndpoint + "GetStepLogsSignedBlobURL";
|
||||||
|
public static readonly string CreateStepLogsMetadata = ResultsReceiverTwirpEndpoint + "CreateStepLogsMetadata";
|
||||||
|
public static readonly string GetJobLogsSignedBlobURL = ResultsReceiverTwirpEndpoint + "GetJobLogsSignedBlobURL";
|
||||||
|
public static readonly string CreateJobLogsMetadata = ResultsReceiverTwirpEndpoint + "CreateJobLogsMetadata";
|
||||||
|
|
||||||
|
public static readonly string AzureBlobSealedHeader = "x-ms-blob-sealed";
|
||||||
|
public static readonly string AzureBlobTypeHeader = "x-ms-blob-type";
|
||||||
|
public static readonly string AzureBlockBlob = "BlockBlob";
|
||||||
|
public static readonly string AzureAppendBlob = "AppendBlob";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user