private static async Task MessageHandler(ProcessMessageEventArgs args) { Console.WriteLine($"{LogNow} Processing message '{args.Message}'"); var message = args.Message; JobPayload jobPayload; DevopsMessage devopsMessage = null; Job driverJob = null; try { // The DevopsMessage does the communications with AzDo devopsMessage = new DevopsMessage(message); // The Body contains the parameters for the application to run // We can't use message.Body.FromObjectAsJson since the raw json returned by AzDo is not valid jobPayload = JobPayload.Deserialize(message.Body.ToArray()); // The only way to detect if a Task still needs to be executed is to download all the details of all tasks (there is no API to retrieve the status of a single task. var records = await devopsMessage.GetRecordsAsync(); if (records == null) { Console.ForegroundColor = ConsoleColor.DarkRed; Console.WriteLine($"{LogNow} Could not retrieve records..."); Console.ResetColor(); return; } var record = records.Value.FirstOrDefault(x => x.Id == devopsMessage.TaskInstanceId); if (record != null && record.State == "completed") { Console.WriteLine($"{LogNow} Job is completed ({record.Result}), skipping..."); // Mark the message as completed await args.CompleteMessageAsync(message); } else { if (!String.IsNullOrWhiteSpace(jobPayload.Condition)) { try { var condition = Engine.Execute(jobPayload.Condition).GetCompletionValue().AsBoolean(); if (!condition) { await devopsMessage?.SendTaskCompletedEventAsync(DevopsMessage.ResultTypes.Skipped); Console.WriteLine($"{LogNow} Job skipped based on condition [{jobPayload.Condition}]"); // Mark the message as completed await args.CompleteMessageAsync(message); return; } } catch { Console.WriteLine($"{LogNow} Could not evaluate codition [{jobPayload.Condition}], ignoring ..."); } } // Inform AzDo that the job is started await devopsMessage.SendTaskStartedEventAsync(); var arguments = String.Join(' ', jobPayload.Args); Console.WriteLine($"{LogNow} Invoking crank with arguments: {arguments}"); // The DriverJob manages the application's lifetime and standard output driverJob = new Job("crank", arguments); driverJob.OnStandardOutput = log => Console.WriteLine(log); driverJob.Start(); // Pump application standard output while it's running while (driverJob.IsRunning) { if ((DateTime.UtcNow - driverJob.StartTimeUtc) > jobPayload.Timeout) { throw new Exception($"{LogNow} Job timed out ({jobPayload.Timeout}). The timeout can be increased in the queued message."); } var logs = driverJob.FlushStandardOutput().ToArray(); // Send any page of logs to the AzDo task log feed if (logs.Any()) { var success = await devopsMessage.SendTaskLogFeedsAsync(String.Join("\r\n", logs)); if (!success) { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"{LogNow} SendTaskLogFeedsAsync failed. If the task was canceled, this jobs should be stopped."); Console.ResetColor(); driverJob.Stop(); } } // Check if task is still active (not canceled) records = await devopsMessage.GetRecordsAsync(); record = records.Value.FirstOrDefault(x => x.Id == devopsMessage.TaskInstanceId); if (record != null && record?.State == "completed") { Console.WriteLine($"{LogNow} Job is completed ({record.Result}), interrupting..."); driverJob.Stop(); } await Task.Delay(TaskLogFeedDelay); } // Mark the task as completed await devopsMessage.SendTaskCompletedEventAsync(driverJob.WasSuccessful?DevopsMessage.ResultTypes.Succeeded : DevopsMessage.ResultTypes.Failed); // Create a task log entry var taskLogObjectString = await devopsMessage?.CreateTaskLogAsync(); if (String.IsNullOrEmpty(taskLogObjectString)) { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"{LogNow} CreateTaskLogAsync failed. The job is probably canceled."); Console.ResetColor(); } else { var taskLogObject = JsonSerializer.Deserialize <Dictionary <string, object> >(taskLogObjectString); var taskLogId = taskLogObject["id"].ToString(); await devopsMessage?.AppendToTaskLogAsync(taskLogId, driverJob.OutputBuilder.ToString()); // Attach task log to the timeline record await devopsMessage?.UpdateTaskTimelineRecordAsync(taskLogObjectString); } // Mark the message as completed await args.CompleteMessageAsync(message); driverJob.Stop(); Console.WriteLine($"{LogNow} Job completed"); } } catch (Exception e) { Console.WriteLine($"{LogNow} Job failed: {e}"); Console.WriteLine("Stopping the task and releasing the message..."); try { await devopsMessage?.SendTaskCompletedEventAsync(DevopsMessage.ResultTypes.Failed); } catch (Exception f) { Console.WriteLine($"{LogNow} Failed to complete the task: {f}"); } try { // TODO: Should the message still be copmleted instead of abandonned? await args.AbandonMessageAsync(message); } catch (Exception f) { Console.WriteLine($"{LogNow} Failed to abandon the message: {f}"); } } finally { try { driverJob?.Dispose(); } catch (Exception e) { Console.WriteLine($"{LogNow} Failed to dispose the job : {e}"); } } }