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}"); } } }
private static async Task MessageHandler(ProcessMessageEventArgs args) { Console.WriteLine("Processing message '{0}'", args.Message.ToString()); 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()); await devopsMessage.SendTaskStartedEventAsync(); var arguments = String.Join(' ', jobPayload.Args); Console.WriteLine("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); Console.WriteLine("Processing..."); driverJob.Start(); // Pump application standard output while it's running while (driverJob.IsRunning) { if ((DateTime.UtcNow - driverJob.StartTimeUtc) > jobPayload.Timeout) { throw new Exception("Job timed out. 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("SendTaskLogFeedsAsync failed. If the task was canceled, this jobs should be ignored stopped."); Console.ResetColor(); } } await Task.Delay(TaskLogFeedDelay); } // Mark the task as completed await devopsMessage.SendTaskCompletedEventAsync(succeeded : driverJob.WasSuccessful); // Create a task log entry var taskLogObjectString = await devopsMessage?.CreateTaskLogAsync(); if (String.IsNullOrEmpty(taskLogObjectString)) { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("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("Job completed"); } catch (Exception e) { Console.WriteLine("Job failed: " + e.ToString()); Console.WriteLine("Stopping the task and releasing the message..."); try { await devopsMessage?.SendTaskCompletedEventAsync(succeeded : false); } catch (Exception f) { Console.WriteLine("Failed to complete the task: " + f.ToString()); } try { // TODO: Should the message still be copmleted instead of abandonned? await args.AbandonMessageAsync(message); } catch (Exception f) { Console.WriteLine("Failed to abandon the message: " + f.ToString()); } } finally { driverJob?.Dispose(); } }
public static int Main(string[] args) { var app = new CommandLineApplication(); app.HelpOption("-h|--help"); var connectionStringOption = app.Option("-c|--connection-string <string>", "The Azure Service Bus connection string. Can be an environment variable name.", CommandOptionType.SingleValue).IsRequired(); var queueOption = app.Option("-q|--queue <string>", "The Azure Service Bus queue name. Can be an environment variable name.", CommandOptionType.SingleValue).IsRequired(); app.OnExecuteAsync(async cancellationToken => { ConnectionString = connectionStringOption.Value(); // Substitute with ENV value if it exists if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(ConnectionString))) { ConnectionString = Environment.GetEnvironmentVariable(ConnectionString); } Queue = queueOption.Value(); // Substitute with ENV value if it exists if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(Queue))) { Queue = Environment.GetEnvironmentVariable(Queue); } var client = new ServiceBusClient(ConnectionString); var processor = client.CreateProcessor(Queue, new ServiceBusProcessorOptions { AutoComplete = false, MaxConcurrentCalls = 1, // Process one message at a time }); // Whenever a message is available on the queue processor.ProcessMessageAsync += async args => { Console.WriteLine("Processing message '{0}'", args.Message.ToString()); 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 jobPayload = JobPayload.Deserialize(message.Body.ToBytes().ToArray()); await devopsMessage.SendTaskStartedEventAsync(); var arguments = String.Join(' ', jobPayload.Args); Console.WriteLine("Invoking application with arguments: " + arguments); // The DriverJob manages the application's lifetime and standard output driverJob = new Job("crank.exe", arguments); driverJob.OnStandardOutput = log => Console.WriteLine(log); Console.WriteLine("Processing..."); driverJob.Start(); // Pump application standard output while it's running while (driverJob.IsRunning) { if ((DateTime.UtcNow - driverJob.StartTimeUtc) > jobPayload.Timeout) { throw new Exception("Job timed out"); } var logs = driverJob.FlushStandardOutput().ToArray(); // Send any page of logs to the AzDo task log feed if (logs.Any()) { await devopsMessage.SendTaskLogFeedsAsync(String.Join("\r\n", logs)); } await Task.Delay(TaskLogFeedDelay); } // Mark the task as completed await devopsMessage.SendTaskCompletedEventAsync(succeeded: driverJob.WasSuccessful); // Create a task log entry var taskLogObjectString = await devopsMessage?.CreateTaskLogAsync(); 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("Job completed"); } catch (Exception e) { Console.WriteLine("Job failed: " + e.Message); try { await devopsMessage?.SendTaskCompletedEventAsync(succeeded: false); await args.AbandonMessageAsync(message); } catch (Exception f) { Console.WriteLine("Failed to abandon task: " + f.Message); } } finally { driverJob?.Dispose(); } }; processor.ProcessErrorAsync += args => { Console.WriteLine("Process error: " + args.Exception.ToString()); return(Task.CompletedTask); }; await processor.StartProcessingAsync(); Console.WriteLine("Press ENTER to exit..."); Console.ReadLine(); }); return(app.Execute(args)); }