Extending CC.Net to write your own trigger

posted by Bryan on

The default triggers provided by CruiseControl.NET just didn't fit my unique scenario. I needed a trigger that could check a database table, and if rows exist - to force a build. Check the low down...

We are working on a dashboard that will queue up NUnit categories for a suite run. The categories will be determined dynamically - and so I couldn't rely on some hard-coded NUnit configurations as we've done in the past while trying to keep things simple.

To work around this, I add the build details to a SQL Server database as a queuing spot. In order to leverage some of CruiseControl.NET's goodness, I wanted a trigger to kick off a build if rows existed. I tried to load the datatable onto a page of the Dashboard - and update the HTTP Header Last Modified Date to work with the UrlTrigger, but that didn't work quite right.

So I created my own.

  1. Create new solution.
  2. Add references to NetReflector.dll, ThoughtWorks.CruiseControl.Core.dll, ThoughtWorks.CruiseControl.Remote.dll.
  3. Create a new class, such as MyTrigger
  4. Implement the ITrigger interface.
  5. Implement each of the interface's members.
  6. Note: Be sure to add the attribute declaration at the class level : [ReflectorType("myTrigger")]
  7. Build
  8. Copy .dll to CC.Net's main folder (C:Program FilesCruiseControl.NETserver).
  9. Rename dll to "ccnet.MyTrigger.plugin.dll"
  10. In your ccnet.config, reference the trigger.

using System;
using Exortech.NetReflector;
using ThoughtWorks.CruiseControl.Core.Util;
using ThoughtWorks.CruiseControl.Remote;

namespace DatabaseTrigger
{
    [ReflectorType("myTrigger")]
    public class MyTrigger : ITrigger
    {
        //member variables here

        public MyTrigger()
        {
            
        }

        [ReflectorProperty("buildCondition", Required = false)]
        public BuildCondition BuildCondition = BuildCondition.IfModificationExists;

        public void IntegrationCompleted()
        {
            IncrementNextBuildTime();
        }

        public IntegrationRequest Fire()
        {
            BuildCondition buildCondition = ShouldRunIntegration();
            if (buildCondition == BuildCondition.NoBuild) return null;
            return new IntegrationRequest(buildCondition, Name, null);
        }

        private BuildCondition ShouldRunIntegration()
        {
            if (databaseRowExists())
                return BuildCondition.ForceBuild;

            return BuildCondition;
        }

        private bool databaseRowExists()
        {
            return parser.CheckForRowsInQueue();
        }

        public DateTime NextBuild
        {
            get { return nextBuildTime; }
        }

        /// <summary>
        /// The name of the trigger. This name is passed to external tools as a means to identify the trigger that requested the build.
        /// </summary>
        /// <version>1.1</version>
        /// <default>IntervalTrigger</default>
        [ReflectorProperty("name", Required = false)]
        public virtual string Name
        {
            get
            {
                if (name == null) name = GetType().Name;
                return name;
            }
            set { name = value; }
        }

        /// <summary>
        /// The number of seconds after an integration cycle completes before triggering the next integration cycle.
        /// </summary>
        /// <version>1.0</version>
        /// <default>60</default>
        [ReflectorProperty("seconds", Required = false)]
        public double IntervalSeconds
        {
            get { return intervalSeconds; }
            set
            {
                intervalSeconds = value;
                IncrementNextBuildTime();
            }
        }

        protected DateTime IncrementNextBuildTime()
        {
            double delaySeconds;
            if (isInitialInterval)
                delaySeconds = InitialIntervalSeconds;
            else
                delaySeconds = IntervalSeconds;

            return nextBuildTime = dateTimeProvider.Now.AddSeconds(delaySeconds);
        }

        /// <summary>
        /// The delay (in seconds) from CCNet startup to the first check for modifications.
        /// </summary>
        /// <version>1.4</version>
        /// <default>Defaults to the IntervalSettings value.</default>
        [ReflectorProperty("initialSeconds", Required = false)]
        public double InitialIntervalSeconds
        {
            get
            {
                if (initialIntervalSeconds == -1)
                    return IntervalSeconds;     // If no setting for this, use IntervalSeconds instead.
                else
                    return initialIntervalSeconds;
            }
            set
            {
                initialIntervalSeconds = value;
                IncrementNextBuildTime();
            }
        } 
    }
}

In the ccnet.config file :

<triggers>
<mytrigger name="dashboard" seconds="30" buildcondidtion="ForceBuild" initialseconds="30">
</mytrigger></triggers>

Leave a comment

blog comments powered by Disqus