Gradle deployment script for Grails webapp

Please note the “Update” section below.

I haven’t had much success with finding useful deployment strategies and/or scripts for Grails anywhere. The extent of the documentation I’ve been able locate for deployment simply tells you to create a WAR and upload it to the servlet container.

Not terribly helpful if you want to run a formal process.

So, for my Grails webapps, I came up with this. I create a file in the “gradle” directory named “deploy.gradle” containing the following:

buildscript {
	repositories {
		jcenter()
	}
	
	dependencies {
		classpath 'org.hidetake:gradle-ssh-plugin:2.9.0'
	}
}

apply plugin: 'org.hidetake.ssh'

// the below is in it's own ext block because it can potentially be
// overridden by .deployrc.gradle in the user's home directory, and the scmUser
// might be required in the following ext block (where the repository is
// set up)
ext {
	scmUser = project.hasProperty('user') ? project.getProperty('user') : System.properties['user.name']
}

// please note that the file must end in ".gradle" or Gradle won't know how
// to process it, and you'll get a "String index out of bounds: 0" error
def rcFile = new File("${System.properties['user.home']}/.deployrc.gradle")
if (rcFile.exists()) {
	apply from: rcFile.absolutePath
}

ext {
	timeNow = new Date().format('yyyyMMddHHmmss')
	// TODO: the below should use a repository directly associated with the individual user
	scmRepository = "${project.scmUser}@my.site:/var/repos/${project.name}.git"
	runEnvironment = project.hasProperty('env') ? project.getProperty('env') : 'production'
	scmBranch = project.hasProperty('branch') ? project.getProperty('branch') : 'master'
	tmpDir = System.getProperty('java.io.tmpdir') + "${project.name}/${timeNow}"
}

apply from: "${project.projectDir}/gradle/deploy/${project.runEnvironment}.gradle"

task deploy {
	group 'Remote Deployment'
	description 'Deploys a branch from your SCM to a target environment on a remote server'
	
	doLast {
		println "Deploying from branch ${project.scmBranch} to environment ${project.runEnvironment}"
		
		ssh.run {
			session(remotes.role('webserver')) {
				// upload the WAR to the target server(s)
				println 'Uploading WAR file'
				put from: "${project.buildDir}/libs/mercury.war", into: "mercury/mercury-${timeNow}.war"
				// stop Tomcat so we can get this party started
				println 'Stopping Tomcat'
				execute 'sudo service tomcat8 stop'
				// if a pre-existing link exists to "current", remove it
				println 'Symlinking'
				execute 'if [ -f mercury/mercury-current.war ]; then rm mercury/mercury-current.war; fi'
				// link our new WAR to the name "current"
				execute "cd mercury; ln -s mercury-${timeNow}.war mercury-current.war"
				// Tomcat doesn't clean up extracted WAR file directories, so we must flush out the /var/lib/tomcat8/webapps directory
				execute 'if [ -d /var/lib/tomcat8/mercury ]; then sudo rm -rf /var/lib/tomcat8/mercury; fi'
				// Fire tomcat back up
				println 'Starting Tomcat'
				execute 'sudo service tomcat8 start'
			}
		}
	}
}

task deployBuild << {
	println "Building WAR for ${project.runEnvironment} environment"
	
	exec {
		workingDir project.tmpDir
		environment 'TERM', 'dumb'	// this prevents grails+gradle for displaying a second progress bar during the following invocation
	
		commandLine 'grails', "-Dgrails.env=${project.runEnvironment}", 'war', 'mercury.war'
	}
}

task deployCheckout << {
	println "Cloning ${project.scmRepository}#${project.scmBranch} for user ${project.scmUser} into ${project.tmpDir}"
	
	exec {
		commandLine 'git', 'clone', '-b', project.scmBranch, project.scmRepository, project.tmpDir
	}
}

task deployCleanup << {
	println "Removing temporary directory ${project.tmpDir}"

	exec {
		commandLine 'rm', '-rf', project.tmpDir
	}
}

deployBuild.dependsOn deployCheckout
deploy.dependsOn deployBuild
deploy.finalizedBy deployCleanup

Also in the “gradle” directory is a subdirectory named “deploy” where I have the files specific to the environments to which I can deploy, such as “staging.gradle”:

remotes {
	web {
		role 'webserver'
		host = 'my.site'
		user = 'webapps'
		identity = file("${System.properties['user.home']}/.ssh/webapps")
	}
}

Now, you will need to make some modifications to your main “build.gradle” script. First, add in the SSH plugin dependency, immediately after the “buildscript” section:

plugins {
    id 'org.hidetake.ssh' version '2.10.1'
}

And then in the area where you have all the “apply” instructions:

apply from: "${project.projectDir}/gradle/deploy.gradle"

Using the script above, I can deploy a particular branch from within my git repository to a specific environment thus:

gradle -Penv=staging -Pbranch=hotfix/1.2.3.001 deploy

It’s probably not perfect, but since I’m new to Gradle and Grails, I think it’s a pretty good start!

Update!

Alas the “org.hidetake.ssh” plugin has not kept up to date with the latest updates to SSH, and as such the above scripts no longer work, and I have abandoned any attempts to use Gradle to effect deployments. Instead, I have switched over to using Ansible to deal with all of this.