mirror of
https://github.com/actions/runner.git
synced 2025-12-12 05:37:01 +00:00
Receive error body from Run Service (#3342)
This commit is contained in:
@@ -1539,6 +1539,26 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationJobUnprocessableException", "GitHub.DistributedTask.WebApi.TaskOrchestrationJobUnprocessableException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||||
|
public sealed class TaskOrchestrationJobUnprocessableException : DistributedTaskException
|
||||||
|
{
|
||||||
|
public TaskOrchestrationJobUnprocessableException(String message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskOrchestrationJobUnprocessableException(String message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskOrchestrationJobUnprocessableException(SerializationInfo info, StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationPlanSecurityException", "GitHub.DistributedTask.WebApi.TaskOrchestrationPlanSecurityException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationPlanSecurityException", "GitHub.DistributedTask.WebApi.TaskOrchestrationPlanSecurityException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||||
public sealed class TaskOrchestrationPlanSecurityException : DistributedTaskException
|
public sealed class TaskOrchestrationPlanSecurityException : DistributedTaskException
|
||||||
|
|||||||
17
src/Sdk/RSWebApi/Contracts/RunServiceError.cs
Normal file
17
src/Sdk/RSWebApi/Contracts/RunServiceError.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.Actions.RunService.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class RunServiceError
|
||||||
|
{
|
||||||
|
[DataMember(Name = "source", EmitDefaultValue = false)]
|
||||||
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "statusCode", EmitDefaultValue = false)]
|
||||||
|
public int Code { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "errorMessage", EmitDefaultValue = false)]
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
|
readErrorBody: true,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
@@ -93,14 +94,35 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||||
|
{
|
||||||
|
switch ((HttpStatusCode)error.Code)
|
||||||
|
{
|
||||||
|
case HttpStatusCode.NotFound:
|
||||||
|
throw new TaskOrchestrationJobNotFoundException($"Job message not found '{messageId}'. {error.Message}");
|
||||||
|
case HttpStatusCode.Conflict:
|
||||||
|
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired '{messageId}'. {error.Message}");
|
||||||
|
case HttpStatusCode.UnprocessableEntity:
|
||||||
|
throw new TaskOrchestrationJobUnprocessableException($"Unprocessable job '{messageId}'. {error.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary back compat
|
||||||
switch (result.StatusCode)
|
switch (result.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.NotFound:
|
case HttpStatusCode.NotFound:
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
||||||
case HttpStatusCode.Conflict:
|
case HttpStatusCode.Conflict:
|
||||||
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}");
|
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}");
|
||||||
default:
|
}
|
||||||
throw new Exception($"Failed to get job message: {result.Error}");
|
|
||||||
|
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to get job message: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to get job message: {result.Error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +130,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
Guid planId,
|
Guid planId,
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
TaskResult result,
|
TaskResult conclusion,
|
||||||
Dictionary<String, VariableValue> outputs,
|
Dictionary<String, VariableValue> outputs,
|
||||||
IList<StepResult> stepResults,
|
IList<StepResult> stepResults,
|
||||||
IList<Annotation> jobAnnotations,
|
IList<Annotation> jobAnnotations,
|
||||||
@@ -120,7 +142,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
PlanID = planId,
|
PlanID = planId,
|
||||||
JobID = jobId,
|
JobID = jobId,
|
||||||
Conclusion = result,
|
Conclusion = conclusion,
|
||||||
Outputs = outputs,
|
Outputs = outputs,
|
||||||
StepResults = stepResults,
|
StepResults = stepResults,
|
||||||
Annotations = jobAnnotations,
|
Annotations = jobAnnotations,
|
||||||
@@ -130,22 +152,39 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
requestUri = new Uri(requestUri, "completejob");
|
requestUri = new Uri(requestUri, "completejob");
|
||||||
|
|
||||||
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
||||||
var response = await SendAsync(
|
var result = await Send2Async(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri,
|
requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
if (response.IsSuccessStatusCode)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (response.StatusCode)
|
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||||
|
{
|
||||||
|
switch ((HttpStatusCode)error.Code)
|
||||||
|
{
|
||||||
|
case HttpStatusCode.NotFound:
|
||||||
|
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary back compat
|
||||||
|
switch (result.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.NotFound:
|
case HttpStatusCode.NotFound:
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||||
default:
|
}
|
||||||
throw new Exception($"Failed to complete job: {response.ReasonPhrase}");
|
|
||||||
|
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to complete job: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to complete job: {result.Error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +208,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri,
|
requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
|
readErrorBody: true,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
@@ -176,12 +216,29 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||||
|
{
|
||||||
|
switch ((HttpStatusCode)error.Code)
|
||||||
|
{
|
||||||
|
case HttpStatusCode.NotFound:
|
||||||
|
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary back compat
|
||||||
switch (result.StatusCode)
|
switch (result.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.NotFound:
|
case HttpStatusCode.NotFound:
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||||
default:
|
}
|
||||||
throw new Exception($"Failed to renew job: {result.Error}");
|
|
||||||
|
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to renew job: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to renew job: {result.Error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,5 +247,36 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryParseErrorBody(string errorBody, out RunServiceError error)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(errorBody))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
error = JsonUtility.FromString<RunServiceError>(errorBody);
|
||||||
|
if (error?.Source == "actions-run-service")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Truncate(string errorBody)
|
||||||
|
{
|
||||||
|
if (errorBody.Length > 100)
|
||||||
|
{
|
||||||
|
return errorBody.Substring(0, 100) + "[truncated]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorBody;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace Sdk.WebApi.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected async Task<RawHttpClientResult> Send2Async(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
HttpContent content = null,
|
HttpContent content = null,
|
||||||
@@ -109,7 +109,47 @@ namespace Sdk.WebApi.WebApi
|
|||||||
Object userState = null,
|
Object userState = null,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
return SendAsync<T>(method, null, requestUri, content, queryParameters, userState, cancellationToken);
|
using (var response = await SendAsync(method, requestUri, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return new RawHttpClientResult(
|
||||||
|
isSuccess: true,
|
||||||
|
error: string.Empty,
|
||||||
|
statusCode: response.StatusCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var errorBody = default(string);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
errorBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorBody = $"Error reading HTTP response body: {ex.Message}";
|
||||||
|
}
|
||||||
|
|
||||||
|
string errorMessage = $"Error: {response.ReasonPhrase}";
|
||||||
|
return new RawHttpClientResult(
|
||||||
|
isSuccess: false,
|
||||||
|
error: errorMessage,
|
||||||
|
statusCode: response.StatusCode,
|
||||||
|
errorBody: errorBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||||
|
HttpMethod method,
|
||||||
|
Uri requestUri,
|
||||||
|
HttpContent content = null,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||||
|
Boolean readErrorBody = false,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
return SendAsync<T>(method, null, requestUri, content, queryParameters, readErrorBody, userState, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||||
@@ -118,18 +158,20 @@ namespace Sdk.WebApi.WebApi
|
|||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
HttpContent content = null,
|
HttpContent content = null,
|
||||||
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||||
|
Boolean readErrorBody = false,
|
||||||
Object userState = null,
|
Object userState = null,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
|
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
|
||||||
using (HttpRequestMessage requestMessage = CreateRequestMessage(method, additionalHeaders, requestUri, content, queryParameters))
|
using (HttpRequestMessage requestMessage = CreateRequestMessage(method, additionalHeaders, requestUri, content, queryParameters))
|
||||||
{
|
{
|
||||||
return await SendAsync<T>(requestMessage, userState, cancellationToken).ConfigureAwait(false);
|
return await SendAsync<T>(requestMessage, readErrorBody, userState, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||||
HttpRequestMessage message,
|
HttpRequestMessage message,
|
||||||
|
Boolean readErrorBody = false,
|
||||||
Object userState = null,
|
Object userState = null,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
@@ -145,8 +187,21 @@ namespace Sdk.WebApi.WebApi
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var errorBody = default(string);
|
||||||
|
if (readErrorBody)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
errorBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorBody = $"Error reading HTTP response body: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string errorMessage = $"Error: {response.ReasonPhrase}";
|
string errorMessage = $"Error: {response.ReasonPhrase}";
|
||||||
return RawHttpClientResult<T>.Fail(errorMessage, response.StatusCode);
|
return RawHttpClientResult<T>.Fail(errorMessage, response.StatusCode, errorBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,27 @@ namespace Sdk.WebApi.WebApi
|
|||||||
public class RawHttpClientResult
|
public class RawHttpClientResult
|
||||||
{
|
{
|
||||||
public bool IsSuccess { get; protected set; }
|
public bool IsSuccess { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A description of the HTTP status code, like "Error: Unprocessable Entity"
|
||||||
|
/// </summary>
|
||||||
public string Error { get; protected set; }
|
public string Error { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTTP response body for unsuccessful HTTP status codes, or an error message when reading the response body fails.
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorBody { get; protected set; }
|
||||||
|
|
||||||
public HttpStatusCode StatusCode { get; protected set; }
|
public HttpStatusCode StatusCode { get; protected set; }
|
||||||
|
|
||||||
public bool IsFailure => !IsSuccess;
|
public bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
protected RawHttpClientResult(bool isSuccess, string error, HttpStatusCode statusCode)
|
public RawHttpClientResult(bool isSuccess, string error, HttpStatusCode statusCode, string errorBody = null)
|
||||||
{
|
{
|
||||||
IsSuccess = isSuccess;
|
IsSuccess = isSuccess;
|
||||||
Error = error;
|
Error = error;
|
||||||
StatusCode = statusCode;
|
StatusCode = statusCode;
|
||||||
|
ErrorBody = errorBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,13 +33,13 @@ namespace Sdk.WebApi.WebApi
|
|||||||
{
|
{
|
||||||
public T Value { get; private set; }
|
public T Value { get; private set; }
|
||||||
|
|
||||||
protected internal RawHttpClientResult(T value, bool isSuccess, string error, HttpStatusCode statusCode)
|
protected internal RawHttpClientResult(T value, bool isSuccess, string error, HttpStatusCode statusCode, string errorBody)
|
||||||
: base(isSuccess, error, statusCode)
|
: base(isSuccess, error, statusCode, errorBody)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RawHttpClientResult<T> Fail(string message, HttpStatusCode statusCode) => new RawHttpClientResult<T>(default(T), false, message, statusCode);
|
public static RawHttpClientResult<T> Fail(string message, HttpStatusCode statusCode, string errorBody) => new RawHttpClientResult<T>(default(T), false, message, statusCode, errorBody);
|
||||||
public static RawHttpClientResult<T> Ok(T value) => new RawHttpClientResult<T>(value, true, string.Empty, HttpStatusCode.OK);
|
public static RawHttpClientResult<T> Ok(T value) => new RawHttpClientResult<T>(value, true, string.Empty, HttpStatusCode.OK, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user