TFS Build (2013) - Build Stop Handling - tfs

I have custom codeactivities in TFS Build. One of them is a background thread that TFS Build does not know about.
I want to find out if there is a way for this thread to check if a "stop build" has been requested. (i.e. check on the current build status WITHOUT needing a CodeActivityContext)
(NB: I can't use the AsyncCodeActivity (and its cancel mechanism) because this still blocks subsequent tasks)
I am currently using a heartbeat system and relying on a timeout of the heatbeat from the TFS Build flow loop but this is not fool proof.
IBuildDetail.BuildFinished exists but there is the catch 22 of if the build is finished, how do you get iBuildDetail?
Because code activities are "stateless", then using a previous "CodeActivityContext" to get iBuildDetail does not work, i.e. the context no longer exists.
I can get to a code path of _buildServer.GetBuild(_buildUri)
but can't find out how to establish your current builduri (not to be confused with build definition, server, agent or number)
Thanks

Good news, I found a solution
I was caught up in the BuildUri term, it turns out at the bottom of iBuildDetails, is "Uri"
This turns out to be the BuildUri
Thus code like this works ...
static private Uri _buildUri;
static private IBuildServer _buildServer;
...
protected override void Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
string ScriptPath = context.GetValue(this.InScriptPath);
_thisContext = context;
IBuildDetail buildDetail = _thisContext.GetExtension<IBuildDetail>();
_buildUri = buildDetail.Uri; // This is the complete string
_buildServer = buildDetail.BuildServer;
......
In background tthread
try
{
IBuildDetail buildDetail = _buildServer.GetBuild(_buildUri); // this does not work as it is not the BuildUri
if (buildDetail != null)
{
if (buildDetail.Status == BuildStatus.Stopped)
{
TerminateProcess();
}
}
}
catch (Exception Ex)
{
TerminateProcess();
}

Related

Calling build.doStop() in prebuild is not stopping fast enough

I am trying to write a Jenkins plugin that can automatically abort a build if that build is triggered on a holiday (or just a given input day/s). The user can configure the days, and each job gets a checkbox that allows the user to decide if they want their job aborted on a holiday or not. Currently my plugin extends JobProperty and utilizes global configuration where I have a list of blacklisted dates. If today is on my list of blacklisted days, then I do not want my job to run. The plugin "works" but with a few annoying caveats.
My main issue is that I can only FAIL the build if it happens to be triggered on a day that's one of my blacklisted days. This is a problem for me because there's no actual error. The job is operating as it should be and I don't want to receive emails full of errors just because the job was halted on a day I didn't want it to run (e.g: a holiday)
When my plugin decides to abort a build, I want to be able to end the job with an "Aborted" status. (In fact - I'd like to be able to control the status and leave it as a potential parameter.)
Below is my prebuild() code.
#Override
public boolean prebuild(AbstractBuild build, BuildListener listener) {
boolean stopped = false;
if(checkIfClosed) {
LocalDate today = LocalDate.now();
listener.getLogger().println("Checking the date for " + DateFormats.yyyyMMdd.print(today));
if (getDescriptor().getUseCalculatedDateChecker()) {
if (!NyseHolidayChecker.isMarketOpen(today)) {
listener.getLogger().println("Closed (From auto calculation)!");
stopped = true;
}
}
if (getDescriptor().getListOfClosedDates() != null && !getDescriptor().getListOfClosedDates().isEmpty()) {
if (getDescriptor().getListOfClosedDates().contains(DateFormats.yyyyMMdd.print(today))) {
listener.getLogger().println("Closed. Date is in the Closed Date List. " +
"If this is wrong check global configuration.");
stopped = true;
}
}
}
if(stopped) {
try {
if(build.doStop() == null) {
return false;
}
} catch (IOException e) {
listener.getLogger().println(e.getMessage());
return false;
} catch (ServletException e) {
listener.getLogger().println(e.getMessage());
return false;
}
//throw new RuntimeException("This job has been told not to run when marked as Closed!");
//throw new AbortException("This job has been told not to run when marked as Closed!");
}
return true;
}
I've tried several different ways to get the job to abort immediately and not have the build marked as failed.
The doc tells me that I should throw an AbortException but that does not appear to be supported by the function I'm overriding.
I also tried to call doStop() but then my first build step (out of 2) still runs at least a little. This isn't desirable because I will never know what state my job will be in when it will be aborted (it could have already sshed somewhere and killed a process...or made something live etc)
What am I missing? I feel like I'm hacking around to get what I need. I am hoping someone can point me in the right direction on how to best do this.
Digging further into Jenkins code showed the Build.doRun() method will execute the build steps in a do while loop which was allowing for a small bit of the build step to get through. The AbortException which Jenkins documentation recommends will also mark a build as failure (undesirable). The only means I found to cancel a job and have it properly mark as just aborted was to throw an InterruptedException. The JobProperties prebuild() function does not allow for any throws (other than Runtime which will mark as failure).
The plugin now extends BuildWrapper. This has both a setUp and preCheckout method that will run prior to build steps executing. These two methods can also throw InterruptedExceptions. Now if my checks pass and the date is blacklisted an InterruptedException is thrown from the interruptOnHoliday(...) method. The BuildWrapper also utilizes a BuildWrapperDescriptor which will place a checkbox in the job configuration based on the name provided in the overridden getDisplayName() function (Acts sort of as a jetty optionalBlock). With this box checked the setUp function can get called, otherwise it will not. This effectively makes the holiday check plugin optional (desired).
To give help to a user on what the Plugin does, include a help.html file in the resources for the plugin, this will allow the programmer to explain to the user how and why to use the Plugin. The BuildWrapperDescriptor class will programmatically know how to use that file.
Hope this helps everyone. Below is the new way to kill :
#Override
public void preCheckout(AbstractBuild build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
interruptOnHoliday(build, listener);
}
/**
* Run a check against todays date and dates from the {#link NyseHolidayChecker}
* which is an automatic calculator for New York Stock Exchange holidays and also a check against the manually
* created user date list. If either one is not checked to be used, then they will be ignored.
*
* #param build {#link hudson.model.AbstractBuild} that we are on, will kill the executor
* #param listener {#link hudson.model.BuildListener} that we will log to
*
* #throws InterruptedException
*/
private void interruptOnHoliday(AbstractBuild build, BuildListener listener) throws InterruptedException {
boolean stopped = false;
LocalDate today = LocalDate.now();
listener.getLogger().println("Checking the date for " + DateFormats.yyyyMMdd.print(today));
//if the NYSE calculator is checked then let's use it
if (getDescriptor().getUseNyseCalculatedDateChecker()) {
if (!NyseHolidayChecker.isMarketOpen(today)) {
listener.getLogger().println("The NYSE is not Open today (From auto calculation)" +
" and this job is marked to abort. Stopping the build!");
stopped = true;
}
}
//If we have inserted manual dates into the list we want to check them
if (getDescriptor().getListOfClosedDates() != null && !getDescriptor().getListOfClosedDates().isEmpty()) {
if (getDescriptor().getListOfClosedDates().contains(DateFormats.yyyyMMdd.print(today))) {
listener.getLogger().println("This date is blacklisted, and this job is marked to abort. " +
"Stopping the job! If this date should not be on the list check Jenkins Settings.");
stopped = true;
}
}
//if we should stop the job then we call doStop() on the build and we also throw an InterruptedException
//The InterruptedException is the only way to abort a build without it failing.
if (stopped) {
try {
build.doStop();
throw new InterruptedException(DateFormats.yyyyMMdd.print(today) + " is a blacklisted date.");
} catch (IOException e) {
listener.getLogger().println(e.getMessage());
} catch (ServletException e) {
listener.getLogger().println(e.getMessage());
}
}
}
#Override
public BuildWrapper.Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
interruptOnHoliday(build, listener);
return new CloseCheckerEnvironment();
}

manually associate changesets in tfs build

Microsoft TFS build by default assigns all changesets after last successful build into "Associated Changesets". Is there a way to do it manually?
What I would like to achieve is to search for last build that has "Build Quality" set to Released.
So Each build would have associated all changesets after latest "Released" build.
Is it possible?
If you are using TFS 2012, the build workflow has an activity "Associate Changesets and Work Items" of type "Microsoft.TeamFoundation.Build.Workflow.Activities.AssociateChangesetsandWorkItems" which gets all the changesets and associate it with the build.
If you want to change that, you need to replace this activity with your custom workflow steps. I will leave the "finding the changesets" part to you but the following activity will allow you to associate the changesets with your build
namespace ContractLibrary
{
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Tracking;
using System;
using System.Activities;
public class WriteAssociatedChangesets : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
if (context == null)
{
return;
}
this.WriteBuildInformation(context);
}
private void WriteBuildInformation(CodeActivityContext context)
{
var buildInformation = new WriteBuildInformation<AssociatedChangeset>()
{
Value = new AssociatedChangeset() { ChangesetId = 17997 /*Your ChangsetsID*/ }
};
context.Track(buildInformation);
}
}
}
The post is really useful in finding out how to write to build information.

How can a Gated check-in be triggered programmatically?

I am getting errors using the Workspace.Checkin command in TFS because the files I am trying to check in are part of a Gated build definition. There are lots of people asking how to override a gated check-in using the TFS API. But a very different question is this: what if I actually want to perform the gated check-in, programmatically? How can I trigger this in TFS API?
What I want to do is:
Perform a normal check-in operation of a set of pending changes
Receive information about the gated build that was launched
Either subscribe to a completion event for that build, or poll the build until it is complete.
Continue if the build was successful, and changes were committed (get the resulting changeset if possible)
Stop if the build was not successful, and report the error.
I could not find anything out there to answer this question; perhaps it is such an edge case that I'm the only one crazy enough to want to do this. That said, my need for this is legitimate. The only thing I can figure is that I would need to go through the lower-level functions of the gated check-in process:
1) Walk through the build definitions to see if the path of any of the files coincide with any of the paths in any of the gated builds.
2) If an intersection exists, create a shelveset of the pending changes
3) Queue a new build of the gated definition using the shelveset
4) Query the build definition and refresh the information until the build status is completed
5) If the build was successful, unshelve the pending changes, and then check-in using an override.
However, note that even if I do these steps, the resulting build won't look like a gated build at all. Policy override notifications will be sent, and the only way of letting someone know that a build was actually performed would be to include such information in the policy override comments. Is there a more direct way to do this that would make it look like any other gated build?
Prompted by the post by Scordo I was able to figure out how to do this. Here is the code:
//Initialize connection to server
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(ServerAddress));
try
{
tpc.EnsureAuthenticated();
}
catch (Exception e)
{
System.Environment.Exit(-1);
}
VersionControlServer vcServer = tpc.GetService<VersionControlServer>();
IBuildServer buildServer = tpc.GetService<IBuildServer>();
//.
//.
//Program logic goes here...
//.
//.
//Get the workspace and the respective changes
Workspace workspace = vcServer.TryGetWorkspace(LocalProjectPath);
PendingChange[] changes = workspace.GetPendingChanges();
//Perform the check-in
bool checkedIn = false;
//Either wait for the gated check-in or continue asynchronously...
bool waitForGatedCheckin = true;
//Attempt a normal check-in
try
{
//First, see what policy failures exist, and override them if necessary
CheckinEvaluationResult result = workspace.EvaluateCheckin(CheckinEvaluationOptions.All,
changes, changes, "Test", null, null);
//Perform the check-in, with overrides if necessary, including Work Item policies
//or include additional code to comply with those policies
PolicyOverrideInfo override = null;
if (result.PolicyFailures != null)
{
override = new PolicyOverrideInfo("override reason", result.PolicyFailures);
}
workspace.CheckIn(changes, comment, null, null, override);
checkedIn = true;
}
catch (GatedCheckinException gatedException)
{
//This exception tells us that a gated check-in is required.
//First, we get the list of build definitions affected by the check-in
ICollection<KeyValuePair<string, Uri>> buildDefs = gatedException.AffectedBuildDefinitions;
if (buildDefs.Count == 1)
{
//If only one affected build definition exists, then we have everything we need to proceed
IEnumerator<KeyValuePair<string, Uri>> buildEnum = buildDefs.GetEnumerator();
buildEnum.MoveNext();
KeyValuePair<string, Uri> buildDef = buildEnum.Current;
String gatedBuildDefName = buildDef.Key;
Uri gatedBuildDefUri = buildDef.Value;
string shelvesetSpecName = gatedException.ShelvesetName;
string[] shelvesetTokens = shelvesetSpecName.Split(new char[] { ';' });
//Create a build request for the gated check-in build
IBuildRequest buildRequest = buildServer.CreateBuildRequest(gatedBuildDefUri);
buildRequest.ShelvesetName = shelvesetTokens[0]; //Specify the name of the existing shelveset
buildRequest.Reason = BuildReason.CheckInShelveset; //Check-in the shelveset if successful
buildRequest.GatedCheckInTicket = gatedException.CheckInTicket; //Associate the check-in
//Queue the build request
IQueuedBuild queuedBuild = buildServer.QueueBuild(buildRequest);
//Wait for the build to complete, or continue asynchronously
if (waitForGatedCheckin)
{
while (!queuedBuild.Build.BuildFinished)
{
//Get the latest status of the build, and pause to yield CPU
queuedBuild.Refresh(QueryOptions.Process);
System.Threading.Thread.Sleep(1000)
}
if (queuedBuild.Build.Status == BuildStatus.Succeeded)
{
checkedIn = true;
}
}
}
else
{
//Determine a method for specifying the appropriate build definition
//if multiple build definitions are affected
}
}
catch (CheckinException checkinException)
{
//Handle other checkin exceptions such as those occurring with locked files,
//permissions issues, etc.
}
if (checkedIn)
{
//Additional logic here, if the check-in was successful
}
I never did it before but this should help you a bit:
When you checkin changes using the workspace and this checkin is protected by a gated build, a GatedCheckinException is thrown. This Exceptions should have all the necessary information to create a shelveset and to trigger the correct build(s) with that shelveset. For Example the AffectedBuildDefinitions property should contain the builddefinitions which are gated build defnitions.
Hope that helps.

What URL will get the status code (result) of the last Jenkins job?

I am wondering if anyone knows what URL is required (as a GET or POST) that will get the status code (result) of the last Jenkins job (when the build# is not known by the client calling the GET request)? I just want to be able to detect if the result was RED or GREEN/BLUE .
I have this code sample, but I need to adjust it so that it works for Jenkins, for this purpose (as stated above):
public class Main {
public static void main(String[] args) throws Exception {
URL url = new URL("http://localhost/jenkins/api/xml");
Document dom = new SAXReader().read(url);
for( Element job : (List<Element>)dom.getRootElement().elements("job")) {
System.out.println(String.format("Name:%s\tStatus:%s",
job.elementText("name"), job.elementText("color")));
}
}
}
Once I figure out the answer, I will share a full example of how I used it. I want to create a job that collects information on a test suite of 20+ jobs and reports on all of them with an email.
You can use the symbolic descriptor lastBuild:
http://localhost/jenkins/job/<jobName>/lastBuild/api/xml
The result element contains a string describing the outcome of the build.

How do I read a multiline value using the Ant 'input' task?

Anyone know how I can enter a multiline value in an Ant script? I'm prompting the user for a Subversion commit comment using the input task, and I'd like to be able to support multiple lines of text.
I'm running the standalone version of Ant at the Windows command prompt.
I thought I might be able to do a search and replace for \n, but I can't see any easy way to do a replace from property value to property value in Ant. It looks like I'd have to write a file, replace in the file, and then load the file into another property. I don't want it that badly.
I'm not 100% positive about this, but I took a look at the Ant source code, and it just does a readLine():
From /org/apache/tools/ant/input/DefaultInputHandler.java:
/**
* Prompts and requests input. May loop until a valid input has
* been entered.
* #param request the request to handle
* #throws BuildException if not possible to read from console
*/
public void handleInput(InputRequest request) throws BuildException {
String prompt = getPrompt(request);
BufferedReader r = null;
try {
r = new BufferedReader(new InputStreamReader(getInputStream()));
do {
System.err.println(prompt);
System.err.flush();
try {
String input = r.readLine();
request.setInput(input);
} catch (IOException e) {
throw new BuildException("Failed to read input from"
+ " Console.", e);
}
} while (!request.isInputValid());
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
throw new BuildException("Failed to close input.", e);
}
}
}
}
Here is what I would do if I were you:
If you are using Ant 1.7, then try implementing your own InputHandler, as described in the documentation. The Apache License permits you to basically copy-and-paste the above code as a starting point.
If you are using Ant 1.6 or earlier, then just create your own MultiLineInput task. You can extend the existing Input class and just read multiple lines.
In either case, you would need to decide how the user indicates "I'm done." You could use a blank line or a period or something.
Good luck!
P.S. When I did a Google search for "ant multi-line input", this page was the first hit :-). Pretty impressive for a question that was asked less than an hour ago.

Resources