Async Http Agentless Task
There are good set of customers who are using agentless HTTP task in their release definitions where they typically invoke an API on another system and that system process the invocation in the same thread. In this flow, the http task succeeds if the API invocation succeeds (Http Status = 200) and fails otherwise. This flow works well when the api returns within the timeout of 20 seconds but there are customers who do heavy lifting in this call which makes the call to go beyond the timeout. For those flows, customers should use the callback value for "completion event" input in the http task as shown below and should use our rest APIs to inform VSTS/TFS about the progress/completion of the task.
In this post, I will talk about what code you can use to post the task status back to VSTS/TFS.
The best solution to post the status back is to use the library that Rakesh Kelkar/Paul Miller/Arun Surredi from ASG team had authored initially and Suresh Tadisetty had enhanced to enable live logging/improving performance w.r.t. offline logs. But if due to some reason, you are not able to use it then you can use the code snippets that Dhashrath GovindaRajan wrote from the following example as well. In this example, I am creating a temporary http server to listen for VSTS requests, posting the request to that server from a release definition, copying the request payload from the http server to a file and then running a console application to read from the file and send task completion to the server.
Here are the steps that you can perform to walk-through this example yourself.
-
- Create a new requestbin to listen for the HTTP requests.
https://requestb.in/1o4380x1
- Create a new http service endpoint in VSTS with the above requestbin URL (without the inspect part) and specify random username/password.
- Create a release definition with a single agentless task using the above service endpoint and with a body as follows.
{ "JobId": "$(system.jobId)", "PlanId": "$(system.planId)", "TimelineId": "$(system.timelineId)", "ProjectId": "$(system.teamProjectId)", "VstsUrl": "$(system.CollectionUri)", "AuthToken": "$(system.AccessToken)" }
- Create a release from this release definition which will post the payload on the request bin URL. This release will remain in progress until you send a task completed message to VSTS/TFS.
- Create a new requestbin to listen for the HTTP requests.
-
- Copy the content of the payload into a file names requestMessage.txt so that the code in subsequent step process it.
- Write a new console application which reads a request and post the status back to VSTS with the logs.
// Read the request from a file
byte[] requestMessageBytes = File.ReadAllBytes(@".\requestMessage.txt");
VSTSMessage message = JsonUtility.Deserialize(requestMessageBytes);
string hubName = "release";
// Today we allow only 1 task to run in a job, so sending jobId works but at some point in time, it will be taskId once we start supporting N tasks in a server phase.
Guid taskInstanceId = message.JobId;
Console.WriteLine($"Connect to VSTS using the auth token in the request message");
VssConnection connection = new VssConnection(new Uri(message.VstsUrl), new VssBasicCredential("username", message.AuthToken));
var taskClient = connection.GetClient();
// Get the plan object
var plan = taskClient.GetPlanAsync(message.ProjectId, hubName, message.PlanId).SyncResult();
// Send the live feed about the task progress to VSTS
StringBuilder logBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i)
{
var feed = $"Hello {i}";
logBuilder.AppendLine(feed);
var feedsWrapper = new VssJsonCollectionWrapper<IEnumerable>(new List() { feed });
taskClient.AppendTimelineRecordFeedAsync(message.ProjectId, hubName, message.PlanId, plan.Timeline.Id, message.JobId, feedsWrapper).SyncResult();
}
// Find the timeline record for the http task so that you can link your offline records with it.
var timeLineRecords = taskClient.GetRecordsAsync(message.ProjectId, hubName, message.PlanId, message.PlanId).SyncResult();
var httpTaskTimeLineRecord = timeLineRecords.Where(record => record.ParentId != null).FirstOrDefault();
// Send the offline logs.
Console.WriteLine("Uploading the log for offline viewing");
var logPath = string.Format(CultureInfo.InvariantCulture, "logs\\{0:D}", httpTaskTimeLineRecord.Id);
var tasklog = new TaskLog(logPath);
var log = taskClient.CreateLogAsync(message.ProjectId, hubName, message.PlanId, tasklog).SyncResult();
using (var ms = new MemoryStream())
{
var allBytes = Encoding.UTF8.GetBytes(logBuilder.ToString());
ms.Write(allBytes, 0, allBytes.Length);
ms.Position = 0;
taskClient.AppendLogContentAsync(message.ProjectId, hubName, message.PlanId, log.Id, ms).SyncResult();
}
// Send task completion event
taskClient.RaisePlanEventAsync(message.ProjectId, hubName, message.PlanId, new TaskCompletedEvent(message.JobId, taskInstanceId, TaskResult.Succeeded)).SyncResult();
The complete sample for this code is here.
Enjoy !!