There is plenty to like about Gradle. Plenty of bloggers have been singing its praises for build. But I see less discussion of it’s usefulness in the deployment pipeline. When in developer role, Groovy is my language of choice.. When I switch to my operations role, I prefer to stay in Groovy. Call me lazy, but I’ve never gotten as proficient with bash as I am with Groovy. Gradle provides a great way to collect and manage scripts for operations.

Gradle Advantages for ops Scripting

When your scripting, you invariably have dependencies that the script requires. Using gradle wrapper, Gradle itself and all your dependencies can be acquired for free. Whether you’re deploying to a JEE container, creating databases or emailing notifications, the bits you need are just a dependency away. The richness of Gradle plugins and the entire GDK/JDK and libraries means anything you need are right at hand.

Although I usually script without an IDE, for more sophisticated scripts, having full IDE support with code completion and refactoring can be valuable.

Continuous Deployment with Bamboo and Gradle

We’ve used Atlassian’s Bamboo for years for build and formerly used the "build plans" to also manage deployment by treating "Stages" as build/deployment. But it was always difficult handling multiple environments (QA, Staging, Production) via build plans.

Bamboo’s (v5?) formal support for Deployment projects makes true Continuous Deployment much more manageable. However, although I love Bamboo, I always felt at the mercy of the plugin developers. The bundled and third-party plugins never seem to do exactly what I want. Further, if your deployment depends on Bamboo plugins, then it’s hard to test and run manual deployments.

So these days, I use Bamboo to define environments and move the build artifacts around and have Bamboo execute Gradle tasks for the actual deployments. For example, instead of using Bamboo’s Tomcat plugin, the Gradle Cargo plugin provides more flexibility.

I have a special devops VCS repo with my "DevOps" gradle script and deployment related scripts. I only need Bamboo to clone the devops repo and everthing else is provided by the wrapper and dependencies. So I can run my deployments from nearly any box with almost no setup.

Gradle Rules vs Properties

One of the nice features of Gradle that I think goes under used is Rules. Most Gradle examples I see emphasize gradle properties or environment variables for configuration. Certainly, these are very useful. However, over use of properties leads to frequently grep-ing the build script for which property to set. If you run multiple tasks on one line, the number of properties gets unwieldily.

If you use rules for common parameters you can make your tasks self documenting and easier to use. As an example, I recently wanted to implement a "rolling" upgrade of a Tomcat cluster. (See earlier post on Load Balancing)

The requirements for a rolling upgrade are:

  • Put target server in "drain" mode

  • wait for active sessions to terminate

  • undeploy the app

  • deploy the new war

  • put the target back back in service.

  • repeat for each server

Each of these are relatively simple scripting tasks. Let’s take the "wait for active sessions" piece and write rule for it.

First, I hacked up tomcatsessions.groovy. This is a crude little script which uses Tomcat’s manager app to total the number of active sessions on one or more Tomcat servers. I wrote this originally to monitor the load balancing across the servers. With addition of a -w flag, it will "wait" until a given context’s active sessions reaches zero.

With this in place, I just need a gradle task to execute it. The script needs the server and context. Typically, we’d put these in properties and define them on the command line. But a rule gives us a chance to embedded the configuration in the task name. (My script also needs a manager username/password. Those should be gradle or external properties, but are hard-code in this version).

A rule can be thought of as the Gradle equivalent to Groovy’s missingMethod(). When Gradle sees a task it doesn’t recognize, it calls each rule with the task name. Each rule gets a chance to define the task dynamically. If the tasks exists afterwards, then it behaves like any other task. That is, it can be executed or referenced in a dependsOn() or extended with doLast().

So to create a rule that invokes my script and waits for zero active session, I do:

tasks.addRule("Pattern: waitFor<context>SessionsOn<server>: Stall until zero sessions on Tomcat server at /context") { String taskName ->

    def matcher = ( taskName =~ /waitFor(.*?)SessionsOn(.*)/ )
    if (matcher.matches()) {
        def (context,server) = matcher[0][1..2]

        task(type: JavaExec, taskName) {
            description "Wait for /$context Sessions on Tomcat $server to terminate"
            main = 'devops.tomcatsessions'
            classpath = sourceSets.main.runtimeClasspath

            args "-c /$context"
            args "-w"
            args "$server"
        }
    }
}

With this in place, I can:

./gradlew waitForMyAppSessionsOnMy-Server-01

If I type ./gradlew tasks I can see all my rules and their syntax without wondering which properties to set.

Using combination of properties and rules can make your build scripts more manageable and easier to read.