Octopus Deploy Gradle Plugin

Ben John
Liftric
Published in
4 min readJan 25, 2021

For package uploads to Octopus, if you’re running a build server that is not supported natively you can use the Octopus Command Line (CLI) or the REST API of octopus instead. For Gradle projects we created a small Gradle plugin to help us get a similar, hassle-free experience as the officially supported CI-Servers: octopus-deploy-plugin

Currently it supports the following use-cases:

  • Create and upload package build-information
  • Upload packages
  • Generate build-information Commits from git history.
  • Progress Octopus Deploy Releases

The plugin doesn’t require any Java or Kotlin Compile Plugin to be present and therefore can be used for all kinds of packages, even if not build directly by Gradle.

Let’s look at a small example to get going:

Setup example project

Let’s generate a Spring Boot starter project to get a quick example project going: start.spring.io

It must be a Gradle project and the language must use Kotlin here to generate the Gradle build files in Kotlin (instead of Groovy). The octopus-deploy-plugin can be used with Groovy build files as well, but all examples are using the Gradle Kotlin DSL.

The Spring Web and Spring Boot Actuator dependency must be added as well. This adds a minimal web service and a health endpoint which we can open to verify the project runs successful.

After unzipping the demo project, we can build and run it to verify it works as expected:

./gradlew build
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar

Now we can verify this by calling the health endpoint at http://localhost:8080/actuator/health

Configure the Octopus Deploy plugin

Next we’ll add the Gradle plugin and add basic configuration. The complete configuration uses the Gradle Lazy Configuration approach (aka. Provider API) which enables us to depend on other providers/tasks for configuration without hardcoding the values at Gradle configuration time. Checkout the Build Lifecycle docs for details why the Lazy Configuration approach is the preferable one.

com.liftric.octopus-deploy-plugin is the plugin ID, the current version is 1.6.0:

plugins {
id("org.springframework.boot") version "2.4.0"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.4.10"
kotlin("plugin.spring") version "1.4.10"
id("com.liftric.octopus-deploy-plugin") version "1.6.0"
}

If we call ./gradlew tasks we’ll see the tasks added by the plugin:

Octopus tasks
-------------
commitsSinceLastTag - Calls git log to receive all commits since the previous tag or the first commit of the current history.
createBuildInformation - Creates the octopus build-information file.
firstCommitHash - Calls git log to get the first commit hash of the current history tree
previousTag - Calls git describe to receive the previous tag name. Will fail if no tag is found.
uploadBuildInformation - Uploads the created octopus build-information file.
uploadPackage - Uploads the package to octopus.

Now we have to configure the plugin (top-level extension):

octopus {
packageName.set(project.name)
version.set(project.version.toString())
serverUrl.set("https://<cloud-instance-name>.octopus.app/")
apiKey.set("API-XXXXYYYYZZZZZXXXXYYYYYZZZZZ")
val bootJar by tasks.existing(Jar::class)
pushPackage.set(bootJar.get().archiveFile)
}

For the name (packageName) and version we’re reusing the project values set by the start.spring.io generator (version under the plugins block, the name is in settings.gradle.kts configured). For automatic versioning something like the researchgate/gradle-release plugin might be used, the static values are alright for our example though.

serverUrl needs the base URL of your Octopus Deploy instance, the example uses the cloud instance naming pattern. See the How to Create an API Key docs for how to create the API key.

In a non-prototype project it’s recommend for security reasons to not store secrets inside the build-script / repository itself. We at Liftric are using Hashicorp Vault as our secrets management tool of choice and even provide a small Gradle Plugin for accessing it: https://github.com/Liftric/vault-client-plugin. Reading a secured / secret environment variable (like Gitlab’s masked variables), or from a repo-external file (e.g. ~/.octopus) might be an alternative to a fully-fledged secrets management solution.

Finally we’re importing the bootJar task provider and binding the archiveFile file property to the pushPackage property so that the plugin knowns which file to upload.

Push build information

The uploadBuildInformation task builds and upload the build-information for the package+version combination.

./gradlew uploadBuildInformation

If you want to debug the generated build-information, calling ./gradlew createBuildInformation only builds it without uploading. The content will be printed but can also be viewed at build/octopus/build-information.json.

Push packages

The uploadPackage task uploads the target pushPackage file:

./gradlew uploadPackage

Octopus Deploy has one quirk during the package upload: it expects the file in the format <name>.<version>.<extension>. In the Maven and Gradle world, the default naming is <name>-<version>.extension. If we leave the default naming of our boot jar, the default name is demo-0.0.1-SNAPSHOT.jar which will upload the package demo-0 with the version 0.1-SNAPSHOT, meaning we have to adapt the naming of our boot jar artifact:

tasks.withType<Jar> {
archiveFileName.set("${archiveBaseName.get().removeSuffix("-")}.${archiveVersion.get()}.${archiveExtension.get()}")
}

Now the correct jar filename is created: demo.0.0.1-SNAPSHOT.jar matching our generated build-information.

Learn more

The complete feature set of the octopus-deploy-plugin is documented in the project’s repo, take a look!

Marcel Kesselring
Liftric GmbH

--

--