Writing Custom Build Trigger For TeamCity

posted by Bryan on

Lately, I've been working on creating a custom build trigger for Team City. We need to monitor an external database for changes which will add a build to the queue. It sure was a lot of work but well worth it.

The links I started with are here: http://confluence.jetbrains.net/display/TCD5/Custom+Build+Trigger and http://confluence.jetbrains.net/display/TCD5/Plugins+Packaging

  1. Create new Java project (I used IntelliJ Community Edition)
  2. Your custom trigger needs to extend BuildTriggerService. Be sure to implement all the abstract members. Also, if you want any UI showing on the webpage, you'll have to override the "getEditParametersUrl" as well (not an abstract method).
  3. Another class needs to extend the PolledBuildTrigger. The abstract method to override is "triggerBuild". This is where your logic would work and then add to the queue by build name.
  4. Another class needs to extend SimplePageExtension which helps convert your .jsp file to .html (to be used by the TeamCity core stuff).
  5. You'll need a Spring bean context file. I named mine: build-server-plugin-CustomTrigger.xml. This is placed according to the article linked above.
  6. I also created a default MANIFEST.MF file and my .jsp file.
  7. You'll want to reference these jars from TeamCity: common-api.jar, server-api.jar, openapi.jar, servlet-api.jar, annotations.jar, jdom.jar, openapi.jar, spring-webmvc.jar, spring.jar, util.jar.
  8. In the Jar Artifact created, you'll want the spring bean xml file, the jsp file, and the code. This is pretty basic stuff.

Here is the code:

package core;
import jetbrains.buildServer.web.openapi.PagePlaces;
import jetbrains.buildServer.web.openapi.SimplePageExtension;
import jetbrains.buildServer.web.util.WebUtil;
import org.jetbrains.annotations.NotNull;
import javax.servlet.http.HttpServletRequest;

public class CustomPageExtension extends SimplePageExtension {
   public CustomPageExtension(PagePlaces pagePlaces) {
       super(pagePlaces);
   }

   @Override
   public boolean isAvailable(@NotNull final HttpServletRequest request) {
       return WebUtil.getPathWithoutAuthenticationType(request).startsWith("/customDetails.html");
   }
}
package core;
import jetbrains.buildServer.buildTriggers.BuildTriggerException;
import jetbrains.buildServer.buildTriggers.PolledBuildTrigger;
import jetbrains.buildServer.buildTriggers.PolledTriggerContext;
import jetbrains.buildServer.serverSide.SBuildType;

public class CustomPolledBuildTrigger extends PolledBuildTrigger {

   private String project;

   public String getProject() {
       return project;
   }

   public void setProject(String project) {
       this.project = project;
   }

   /**
    * Checks to see if a pending build exists.  If yes, it will add the build to the queue.
    */
   @Override
   public void triggerBuild(PolledTriggerContext ptc) throws BuildTriggerException {

       //Logic goes here

       String buildName = getProject() + "_Dynamic";

       if (DatabaseParser.hasPendingRow(getProject().toLowerCase())) {
           ptc.getBuildType().addToQueue(buildName);
       }
   }
}
package core;
import jetbrains.buildServer.buildTriggers.BuildTriggerDescriptor;
import jetbrains.buildServer.buildTriggers.BuildTriggerService;
import jetbrains.buildServer.buildTriggers.BuildTriggeringPolicy;
import org.jetbrains.annotations.NotNull;

public class CustomTrigger extends BuildTriggerService {

   //This file MUST be placed in <Team City install dir>\webapps\ROOT\admin\triggers
   private final String editParametersUrl = "customDetails.jsp";

   private String project;

   public String getProject() {
       return project;
   }

   public void setProject(String project) {
       this.project = project;
   }

   @Override
   @NotNull
   public String getName() {
       return "CustomTrigger";
   }

   @Override
   @NotNull
   public String getDisplayName() {
       return "Our Custom Trigger";
   }

   @Override
   @NotNull
   public String describeTrigger(BuildTriggerDescriptor btd) {
       return "A Custom Trigger to queue up dynamic builds.";
   }

   @Override
   @NotNull
   public BuildTriggeringPolicy getBuildTriggeringPolicy() {
       CustomPolledBuildTrigger tt = new CustomPolledBuildTrigger();
       tt.setProject(getProject());
       return (BuildTriggeringPolicy) tt;
   }

   @Override
   public boolean isMultipleTriggersPerBuildTypeAllowed(){
       return true;
   }

   @Override
   public String getEditParametersUrl(){
       //This is mandatory if you want the web UI to show in the select/edit Trigger Details popup modal.
       return editParametersUrl;
   }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="constructor" xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ">


   <bean id="customTriggerBean" class="core.CustomTrigger">
       <property name="project" value="Custom"/>
   </bean>

   <bean id="polledBuildTriggerBean" class="core.CustomPolledBuildTrigger" scope="session">
       <property name="project" ref="customTriggerBean.project"/>
   </bean>

   <bean id="myExtensionBean" class="core.CustomPageExtension">
           <property name="placeId" value="EDIT_BUILD_RUNNER_SETTINGS_FRAGMENT"/>
           <property name="pluginName" value="CustomTrigger"/>
           <property name="includeUrl" value="customDetails.jsp"/>
   </bean>
</beans>

Leave a comment

blog comments powered by Disqus