From 8ac6b418aaa5ae861f46426460df04d001427453 Mon Sep 17 00:00:00 2001 From: ChristopherHX Date: Fri, 29 Nov 2024 20:28:39 +0100 Subject: [PATCH] add json cli flag for act compat (#449) --- src/Runner.Client/ActionResultStatus.cs | 21 ++++++ .../ActionResultStatusConverter.cs | 29 +++++++ src/Runner.Client/Program.cs | 75 ++++++++++++++++--- .../Controllers/MessageController.cs | 16 ++-- 4 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 src/Runner.Client/ActionResultStatus.cs create mode 100644 src/Runner.Client/ActionResultStatusConverter.cs diff --git a/src/Runner.Client/ActionResultStatus.cs b/src/Runner.Client/ActionResultStatus.cs new file mode 100644 index 00000000000..9f53e4e87c9 --- /dev/null +++ b/src/Runner.Client/ActionResultStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace Runner.Client +{ +[DataContract] +public enum ActionResultStatus +{ + [EnumMember] + Success = 0, + + [EnumMember] + Failure = 2, + + [EnumMember] + Cancelled = 3, + + [EnumMember] + Skipped = 4, +} + +} diff --git a/src/Runner.Client/ActionResultStatusConverter.cs b/src/Runner.Client/ActionResultStatusConverter.cs new file mode 100644 index 00000000000..a4cd59aa45d --- /dev/null +++ b/src/Runner.Client/ActionResultStatusConverter.cs @@ -0,0 +1,29 @@ +using System.Runtime.Serialization; +using GitHub.DistributedTask.WebApi; + +namespace Runner.Client +{ + public static class ActionResultStatusConverter { + public static ActionResultStatus FromPipelines(TaskResult pipelineStatus) { + ActionResultStatus result = ActionResultStatus.Failure; + switch(pipelineStatus) { + case TaskResult.Failed: + case TaskResult.Abandoned: + result = ActionResultStatus.Failure; + break; + case TaskResult.Canceled: + result = ActionResultStatus.Cancelled; + break; + case TaskResult.Succeeded: + case TaskResult.SucceededWithIssues: + result = ActionResultStatus.Success; + break; + case TaskResult.Skipped: + result = ActionResultStatus.Skipped; + break; + } + return result; + } + } +} + diff --git a/src/Runner.Client/Program.cs b/src/Runner.Client/Program.cs index 7138b6bf13b..85e97456a41 100644 --- a/src/Runner.Client/Program.cs +++ b/src/Runner.Client/Program.cs @@ -55,6 +55,7 @@ public class Job { public bool Finished {get;set;} public JobCompletedEvent JobCompletedEvent {get;set;} + public string Matrix { get; set; } } private class TimeLineEvent { @@ -167,7 +168,7 @@ private class Parameters { public bool Interactive { get; internal set; } public string[] LocalRepositories { get; set; } public bool Trace { get; set; } - + public bool Json { get; set; } public Parameters ShallowCopy() { return (Parameters) this.MemberwiseClone(); @@ -975,6 +976,9 @@ static int Main(string[] args) var traceOpt = new Option( new[] {"--trace"}, "Client Trace of console log events, to debug missing live logs"); + var jsonOpt = new Option( + new[] {"--json"}, + "Experimental json logging"); var quietOpt = new Option( new[] {"-q", "--quiet"}, "Display no progress in the cli"); @@ -1104,6 +1108,7 @@ static int Main(string[] args) watchOpt, interactiveOpt, traceOpt, + jsonOpt, quietOpt, privilegedOpt, usernsOpt, @@ -2083,6 +2088,9 @@ await Task.WhenAny(Task.Run(() => { }); } } + if(rec.WorkflowName == null && rec.TimeLine?.FirstOrDefault()?.RecordType == "workflow") { + rec.WorkflowName = rec.TimeLine?.FirstOrDefault()?.RefName; + } return Task.CompletedTask; }; Func printFinishJob = async ev => { @@ -2096,7 +2104,11 @@ await Task.WhenAny(Task.Run(() => { } catch { } - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Job Completed with Status: {ev.Result.ToString()}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = new {}, jobResult = ActionResultStatusConverter.FromPipelines(ev.Result).ToString().ToLower(), msg = $"Job Completed with Status: {ev.Result.ToString()}", time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Job Completed with Status: {ev.Result.ToString()}"); + } }; var eventstream = resp.Content.ReadAsStream(); @@ -2134,8 +2146,12 @@ await Task.WhenAny(Task.Run(() => { if(record == null || !record.Result.HasValue) { rec.Pending.Add(e); continue; - } - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + } + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), stepResult = ActionResultStatusConverter.FromPipelines(record.Result.Value).ToString().ToLower(), msg = $"{record.Result.Value.ToString()}: {record.Name}", stage = "Main", step = rec.TimeLine.Find(r => r.Id == e.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + } } rec.RecordId = e.record.StepId; if(rec.TimeLine != null) { @@ -2150,11 +2166,19 @@ await Task.WhenAny(Task.Run(() => { } catch { } - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Running: {record.Name}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), msg = $"Running: {record.Name}", stage = "Main", step = rec.TimeLine.Find(r => r.Id == e.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null && rec.TimeLine[0].RecordType != "workflow" ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Running: {record.Name}"); + } } } foreach (var webconsoleline in e.record.Value) { - WriteLogLine((int)rec.Color, webconsoleline); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), msg = webconsoleline, stage = "Main", step = rec.TimeLine.Find(r => r.Id == e.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, webconsoleline); + } } } if(line == "event: timeline") { @@ -2174,7 +2198,11 @@ await Task.WhenAny(Task.Run(() => { break; } var rec = timelineRecords[e.timelineId]; - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), stepResult = ActionResultStatusConverter.FromPipelines(record.Result.Value).ToString().ToLower(), msg = $"{record.Result.Value.ToString()}: {record.Name}", stage = "Main", step = rec.TimeLine.Find(r => r.Id == e2.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + } } timelineRecords[e.timelineId].RecordId = e2.record.StepId; if(timelineRecords[e.timelineId].TimeLine != null) { @@ -2189,11 +2217,20 @@ await Task.WhenAny(Task.Run(() => { } catch { } - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Running: {record.Name}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), msg = $"Running: {record.Name}", stage = "Main", step = rec.TimeLine.Find(r => r.Id == e2.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Running: {record.Name}"); + } } } foreach (var webconsoleline in e2.record.Value) { - WriteLogLine((int)timelineRecords[e.timelineId].Color, webconsoleline); + if(parameters.Json) { + var rec = timelineRecords[e.timelineId]; + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), msg = webconsoleline, stage = "Main", step = rec.TimeLine.Find(r => r.Id == e2.record.StepId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)timelineRecords[e.timelineId].Color, webconsoleline); + } } timelineRecords[e.timelineId].Pending.RemoveAt(0); } @@ -2202,7 +2239,11 @@ await Task.WhenAny(Task.Run(() => { if(record != null && record.Result.HasValue) { var rec = timelineRecords[e.timelineId]; rec.RecordId = Guid.Empty; - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, job = rec.TimeLine[0].Name, jobId = rec.TimeLine[0].RefName, level = "info", matrix = JArray.Parse(jobs.FirstOrDefault(j => j.JobId == rec.TimeLine[0].Id)?.Matrix ?? "[{}]").LastOrDefault() ?? new JObject(), stepResult = ActionResultStatusConverter.FromPipelines(record.Result.Value).ToString().ToLower(), msg = $"{record.Result.Value.ToString()}: {record.Name}", stage = "Main", step = rec.TimeLine.Find(r => r.Id == rec.RecordId)?.Name, stepId = new [] { rec.RecordId.ToString() }, time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"{record.Result.Value.ToString()}: {record.Name}"); + } } } } @@ -2334,7 +2375,11 @@ await Task.WhenAny(Task.Run(() => { result = TaskResult.Succeeded; } if(rec != null) { - WriteLogLine((int)rec.Color, $"{(rec.WorkflowName != null ? $"{rec.WorkflowName} / " : "")}{rec.TimeLine[0].Name}", $"Workflow {_workflow.runid} Completed with Status: {result.ToString()}"); + if(parameters.Json) { + Console.WriteLine(JsonConvert.SerializeObject(new { dryrun = false, workflowName = rec.WorkflowName, level = "info", workflowResult = ActionResultStatusConverter.FromPipelines(result).ToString().ToLower(), msg = $"Workflow {_workflow.runid} Completed with Status: {result.ToString()}", time = System.DateTime.Now })); + } else { + WriteLogLine((int)rec.Color, $"{rec.TimeLine[0].Name}", $"Workflow {_workflow.runid} Completed with Status: {result.ToString()}"); + } } else { Console.WriteLine($"Workflow {_workflow.runid} finished with status {result.ToString()}"); } @@ -2345,6 +2390,13 @@ await Task.WhenAny(Task.Run(() => { var job = JsonConvert.DeserializeObject(data); jobs.Add(job); } + if(line == "event: job_update") { + var job = JsonConvert.DeserializeObject(data); + var idx = jobs.FindIndex(j => j.JobId == job.JobId); + if(idx != -1) { + jobs[idx] = job; + } + } if(line == "event: finish") { var ev = JsonConvert.DeserializeObject(data); await printFinishJob(ev); @@ -2485,6 +2537,7 @@ await Task.WhenAny(Task.Run(() => { parameters.Watch = bindingContext.ParseResult.GetValueForOption(watchOpt); parameters.Interactive = bindingContext.ParseResult.GetValueForOption(interactiveOpt); parameters.Trace = bindingContext.ParseResult.GetValueForOption(traceOpt); + parameters.Json = bindingContext.ParseResult.GetValueForOption(jsonOpt); parameters.Quiet = bindingContext.ParseResult.GetValueForOption(quietOpt); parameters.Privileged = bindingContext.ParseResult.GetValueForOption(privilegedOpt); parameters.Userns = bindingContext.ParseResult.GetValueForOption(usernsOpt); diff --git a/src/Runner.Server/Controllers/MessageController.cs b/src/Runner.Server/Controllers/MessageController.cs index 53f8ee43ec3..112a5e847de 100644 --- a/src/Runner.Server/Controllers/MessageController.cs +++ b/src/Runner.Server/Controllers/MessageController.cs @@ -1053,16 +1053,16 @@ private HookResponse ConvertYaml2(string fileRelativePath, string content, strin workflowTimelineId = Guid.NewGuid(); attempt.TimeLineId = workflowTimelineId; _context.SaveChanges(); - var records = new List{ new TimelineRecord{ Id = workflowTimelineId, Name = fileRelativePath } }; + var records = new List{ new TimelineRecord{ Id = workflowTimelineId, Name = fileRelativePath, RefName = fileRelativePath, RecordType = "workflow" } }; TimelineController.dict[workflowTimelineId] = (records, new System.Collections.Concurrent.ConcurrentDictionary>() ); } Guid workflowRecordId = callingJob?.RecordId ?? attempt.TimeLineId; if(workflowTimelineId == attempt.TimeLineId) { // Add workflow as dummy job, to improve early cancellation of Runner.Client - initializingJobs.TryAdd(workflowTimelineId, new Job() { JobId = workflowTimelineId, TimeLineId = workflowTimelineId, runid = runid } ); + initializingJobs.TryAdd(workflowTimelineId, new Job() { JobId = workflowTimelineId, TimeLineId = workflowTimelineId, runid = runid, workflowname = fileRelativePath } ); if(attempt.Attempt > 1) { workflowRecordId = Guid.NewGuid(); - TimelineController.dict[workflowTimelineId] = (new List{ new TimelineRecord{ Id = workflowRecordId, ParentId = workflowTimelineId, Order = attempt.Attempt, Name = $"Attempt {attempt.Attempt}" } }, new System.Collections.Concurrent.ConcurrentDictionary>() ); + TimelineController.dict[workflowTimelineId] = (new List{ new TimelineRecord{ Id = workflowRecordId, ParentId = workflowTimelineId, Order = attempt.Attempt, Name = $"Attempt {attempt.Attempt}", RecordType = "workflow" } }, new System.Collections.Concurrent.ConcurrentDictionary>() ); new TimelineController(_context, Configuration).UpdateTimeLine(workflowTimelineId, new VssJsonCollectionWrapper>(TimelineController.dict[workflowTimelineId].Item1)); } else { new TimelineController(_context, Configuration).UpdateTimeLine(workflowTimelineId, new VssJsonCollectionWrapper>(TimelineController.dict[workflowTimelineId].Item1)); @@ -1997,7 +1997,7 @@ private HookResponse ConvertYaml2(string fileRelativePath, string content, strin var jid = jobitem.Id; jobitem.TimelineId = Guid.NewGuid(); - var jobrecord = new TimelineRecord{ Id = jobitem.Id, Name = jobitem.name }; + var jobrecord = new TimelineRecord{ Id = jobitem.Id, Name = jobitem.name, RefName = jobitem.name, RecordType = "job" }; TimelineController.dict[jobitem.TimelineId] = ( new List{ jobrecord }, new System.Collections.Concurrent.ConcurrentDictionary>() ); var jobTraceWriter = new TraceWriter2(line => TimeLineWebConsoleLogController.AppendTimelineRecordFeed(new TimelineRecordFeedLinesWrapper(jobitem.Id, new List{ line }), jobitem.TimelineId, jobitem.Id)); var jobNameToken = (from r in run where r.Key.AssertString($"jobs.{jobname} mapping key").Value == "name" select r.Value).FirstOrDefault(); @@ -7189,9 +7189,11 @@ public async Task OnSchedule2([FromQuery] string job, [FromQuery] List runid = new List(); var queue2 = Channel.CreateUnbounded>(new UnboundedChannelOptions { SingleReader = true }); var chwriter = queue2.Writer; - Func updateJob = async job => { - if(jobCache.TryAdd(job.JobId, job)) { - await chwriter.WriteAsync(new KeyValuePair("job", JsonConvert.SerializeObject(job, new JsonSerializerSettings{ ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List{new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() }}}))); + Func updateJob = async jobInstance => { + if(jobCache.TryAdd(jobInstance.JobId, jobInstance)) { + await chwriter.WriteAsync(new KeyValuePair("job", JsonConvert.SerializeObject(jobInstance, new JsonSerializerSettings{ ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List{new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() }}}))); + } else { + await chwriter.WriteAsync(new KeyValuePair("job_update", JsonConvert.SerializeObject(jobInstance, new JsonSerializerSettings{ ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List{new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() }}}))); } }; TimeLineWebConsoleLogController.LogFeedEvent handler = async (sender, timelineId2, recordId, record) => {