diff --git a/src/Runner.Common/RunServer.cs b/src/Runner.Common/RunServer.cs index 0f9fa1a01..c1e25d8b8 100644 --- a/src/Runner.Common/RunServer.cs +++ b/src/Runner.Common/RunServer.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using GitHub.Actions.RunService.WebApi; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Sdk; @@ -16,7 +18,7 @@ namespace GitHub.Runner.Common Task GetJobMessageAsync(string id, CancellationToken token); - Task CompleteJobAsync(Guid planId, Guid jobId, CancellationToken token); + Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary outputs, IList stepResults, CancellationToken token); } public sealed class RunServer : RunnerService, IRunServer @@ -56,11 +58,11 @@ namespace GitHub.Runner.Common return jobMessage; } - public Task CompleteJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken) + public Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary outputs, IList stepResults, CancellationToken cancellationToken) { CheckConnection(); return RetryRequest( - async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken); + async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, cancellationToken), cancellationToken); } } } diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index c4a7db6fe..d12cb8e34 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using GitHub.Actions.RunService.WebApi; using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines.ContextData; @@ -437,6 +438,17 @@ namespace GitHub.Runner.Worker PublishStepTelemetry(); + var stepResult = new StepResult(); + stepResult.ExternalID = _record.Id; + stepResult.Conclusion = _record.Result ?? TaskResult.Succeeded; + stepResult.Status = _record.State; + stepResult.Number = _record.Order; + stepResult.Name = _record.Name; + stepResult.StartedAt = _record.StartTime; + stepResult.CompletedAt = _record.FinishTime; + + Global.StepsResult.Add(stepResult); + if (Root != this) { // only dispose TokenSource for step level ExecutionContext @@ -710,6 +722,9 @@ namespace GitHub.Runner.Worker // ActionsStepTelemetry for entire job Global.StepsTelemetry = new List(); + // Steps results for entire job + Global.StepsResult = new List(); + // Job Outputs JobOutputs = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Runner.Worker/GlobalContext.cs b/src/Runner.Worker/GlobalContext.cs index b367383d1..f9120e380 100644 --- a/src/Runner.Worker/GlobalContext.cs +++ b/src/Runner.Worker/GlobalContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using GitHub.Actions.RunService.WebApi; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common.Util; using GitHub.Runner.Worker.Container; @@ -16,6 +17,7 @@ namespace GitHub.Runner.Worker public IList FileTable { get; set; } public IDictionary> JobDefaults { get; set; } public List StepsTelemetry { get; set; } + public List StepsResult { get; set; } public List JobTelemetry { get; set; } public TaskOrchestrationPlanReference Plan { get; set; } public List PrependPath { get; set; } diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index 49150c5ca..41fc062d3 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -260,7 +260,7 @@ namespace GitHub.Runner.Worker { try { - await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, default); + await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, default); return result; } catch (Exception ex) diff --git a/src/Sdk/RSWebApi/Contracts/AcquireJobRequest.cs b/src/Sdk/RSWebApi/Contracts/AcquireJobRequest.cs new file mode 100644 index 000000000..022f34b42 --- /dev/null +++ b/src/Sdk/RSWebApi/Contracts/AcquireJobRequest.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using GitHub.DistributedTask.WebApi; + +namespace GitHub.Actions.RunService.WebApi +{ + [DataContract] + public class AcquireJobRequest + { + [DataMember(Name = "streamId", EmitDefaultValue = false)] + public string StreamID { get; set; } + } +} \ No newline at end of file diff --git a/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs b/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs new file mode 100644 index 000000000..27aa3f963 --- /dev/null +++ b/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using GitHub.DistributedTask.WebApi; + +namespace GitHub.Actions.RunService.WebApi +{ + [DataContract] + public class CompleteJobRequest + { + [DataMember(Name = "planId", EmitDefaultValue = false)] + public Guid PlanID { get; set; } + + [DataMember(Name = "jobId", EmitDefaultValue = false)] + public Guid JobID { get; set; } + + [DataMember(Name = "conclusion")] + public TaskResult Conclusion { get; set; } + + [DataMember(Name = "outputs", EmitDefaultValue = false)] + public Dictionary Outputs { get; set; } + + [DataMember(Name = "stepResults", EmitDefaultValue = false)] + public IList StepResults { get; set; } + } +} \ No newline at end of file diff --git a/src/Sdk/RSWebApi/Contracts/StepResult.cs b/src/Sdk/RSWebApi/Contracts/StepResult.cs new file mode 100644 index 000000000..e7df8d6d4 --- /dev/null +++ b/src/Sdk/RSWebApi/Contracts/StepResult.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using GitHub.DistributedTask.WebApi; + +namespace GitHub.Actions.RunService.WebApi +{ + [DataContract] + public class StepResult + { + [DataMember(Name = "external_id", EmitDefaultValue = false)] + public Guid ExternalID { get; set; } + + [DataMember(Name = "number", EmitDefaultValue = false)] + public int? Number { get; set; } + + [DataMember(Name = "name", EmitDefaultValue = false)] + public string Name { get; set; } + + [DataMember(Name = "status")] + public TimelineRecordState? Status { get; set; } + + [DataMember(Name = "conclusion")] + public TaskResult? Conclusion { get; set; } + + [DataMember(Name = "started_at", EmitDefaultValue = false)] + public DateTime? StartedAt { get; set; } + + [DataMember(Name = "completed_at", EmitDefaultValue = false)] + public DateTime? CompletedAt { get; set; } + + [DataMember(Name = "completed_log_url", EmitDefaultValue = false)] + public string CompletedLogURL { get; set; } + + [DataMember(Name = "completed_log_lines", EmitDefaultValue = false)] + public long? CompletedLogLines { get; set; } + } +} \ No newline at end of file diff --git a/src/Sdk/DTWebApi/WebApi/RunServiceHttpClient.cs b/src/Sdk/RSWebApi/RunServiceHttpClient.cs similarity index 71% rename from src/Sdk/DTWebApi/WebApi/RunServiceHttpClient.cs rename to src/Sdk/RSWebApi/RunServiceHttpClient.cs index 7db46d7be..0bf46146e 100644 --- a/src/Sdk/DTWebApi/WebApi/RunServiceHttpClient.cs +++ b/src/Sdk/RSWebApi/RunServiceHttpClient.cs @@ -1,15 +1,17 @@ -using System; +using System; +using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using GitHub.DistributedTask.Pipelines; +using GitHub.DistributedTask.WebApi; using GitHub.Services.Common; using GitHub.Services.OAuth; using GitHub.Services.WebApi; using Sdk.WebApi.WebApi; -namespace GitHub.DistributedTask.WebApi +namespace GitHub.Actions.RunService.WebApi { - [ResourceArea(TaskResourceIds.AreaId)] public class RunServiceHttpClient : RawHttpClientBase { public RunServiceHttpClient( @@ -52,21 +54,21 @@ namespace GitHub.DistributedTask.WebApi { } - public Task GetJobMessageAsync( + public Task GetJobMessageAsync( Uri requestUri, string messageId, CancellationToken cancellationToken = default) { HttpMethod httpMethod = new HttpMethod("POST"); - var payload = new { + var payload = new AcquireJobRequest + { StreamID = messageId }; requestUri = new Uri(requestUri, "acquirejob"); - var payloadJson = JsonUtility.ToString(payload); - var requestContent = new StringContent(payloadJson, System.Text.Encoding.UTF8, "application/json"); - return SendAsync( + var requestContent = new ObjectContent(payload, new VssJsonMediaTypeFormatter(true)); + return SendAsync( httpMethod, requestUri: requestUri, content: requestContent, @@ -77,18 +79,24 @@ namespace GitHub.DistributedTask.WebApi Uri requestUri, Guid planId, Guid jobId, + TaskResult result, + Dictionary outputs, + IList stepResults, CancellationToken cancellationToken = default) { HttpMethod httpMethod = new HttpMethod("POST"); - var payload = new { - PlanId = planId, - JobId = jobId + var payload = new CompleteJobRequest() + { + PlanID = planId, + JobID = jobId, + Conclusion = result, + Outputs = outputs, + StepResults = stepResults }; requestUri = new Uri(requestUri, "completejob"); - var payloadJson = JsonUtility.ToString(payload); - var requestContent = new StringContent(payloadJson, System.Text.Encoding.UTF8, "application/json"); + var requestContent = new ObjectContent(payload, new VssJsonMediaTypeFormatter(true)); return SendAsync( httpMethod, requestUri, diff --git a/src/Test/L0/Worker/ExecutionContextL0.cs b/src/Test/L0/Worker/ExecutionContextL0.cs index 3aef7fb58..649e66c86 100644 --- a/src/Test/L0/Worker/ExecutionContextL0.cs +++ b/src/Test/L0/Worker/ExecutionContextL0.cs @@ -711,6 +711,63 @@ namespace GitHub.Runner.Common.Tests.Worker } } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void PublishStepResult_EmbeddedStep() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange: Create a job request message. + TaskOrchestrationPlanReference plan = new(); + TimelineReference timeline = new(); + Guid jobId = Guid.NewGuid(); + string jobName = "some job name"; + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null); + jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() + { + Alias = Pipelines.PipelineConstants.SelfAlias, + Id = "github", + Version = "sha1" + }); + jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); + + // Arrange: Setup the paging logger. + var pagingLogger = new Mock(); + var pagingLogger2 = new Mock(); + var jobServerQueue = new Mock(); + jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); + + hc.EnqueueInstance(pagingLogger.Object); + hc.EnqueueInstance(pagingLogger2.Object); + hc.SetSingleton(jobServerQueue.Object); + + var ec = new Runner.Worker.ExecutionContext(); + ec.Initialize(hc); + + // Act. + ec.InitializeJob(jobRequest, CancellationToken.None); + ec.Start(); + + var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true); + embeddedStep.Start(); + + embeddedStep.StepTelemetry.Type = "node16"; + embeddedStep.StepTelemetry.Action = "actions/checkout"; + embeddedStep.StepTelemetry.Ref = "v2"; + + embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }); + embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }); + embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }); + + ec.Complete(); + + // Assert. + Assert.Equal(1, ec.Global.StepsResult.Count); + Assert.Equal(TaskResult.Succeeded, ec.Global.StepsResult.Single().Conclusion); + } + } + private TestHostContext CreateTestContext([CallerMemberName] String testName = "") { var hc = new TestHostContext(this, testName); diff --git a/src/Test/L0/Worker/HandlerL0.cs b/src/Test/L0/Worker/HandlerL0.cs index f1878b2be..e46dfc91c 100644 --- a/src/Test/L0/Worker/HandlerL0.cs +++ b/src/Test/L0/Worker/HandlerL0.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using GitHub.Actions.RunService.WebApi; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Sdk; diff --git a/src/Test/L0/Worker/OutputManagerL0.cs b/src/Test/L0/Worker/OutputManagerL0.cs index e0364e9f0..8bac93933 100644 --- a/src/Test/L0/Worker/OutputManagerL0.cs +++ b/src/Test/L0/Worker/OutputManagerL0.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using GitHub.Actions.RunService.WebApi; using GitHub.Runner.Sdk; using GitHub.Runner.Worker; using GitHub.Runner.Worker.Container;