There are tons of built-in deployment mechanisms provided by Heroku. However if you need or want to have control over your deployment pipeline, and rely on Heroku as a platform implementation detail, this plugin will help you to automate interactions with Heroku API.
Bear in mind that we talk about deploy
in a broader, or e2e, sense. From the creation of the Heroku App until checking that
the dynos are serving the expected content. We provide an opinionated view of a deployment and the building blocks so you
can craft your own deployment task. The only previous requirements for the plugin to work are:
-
Your apps packed on binary form.
-
A Heroku account and its related API key.
You may need the binary for different purposes such as integration testing, QA environment or to support different deployments like enterprise on-premises solutions. The pattern of build the binary only once enables the following goodies:
-
Efficiency through build avoidance.
-
Audit. You may enforce traceability policies to ensure that the binary that you tested against QA is the very same than the one deployed on production.
This plugin embraces the agnostic nature of Heroku, as far as you provide a supported buildpack with a compatible binary.
You can apply the plugin using the plugins DSL
plugins {
id "com.felipefzdz.gradle.heroku" version "1.0.6"
}
Or using the buildscript block
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.felipefzdz.gradle.heroku:gradle-heroku-plugin:1.0.6"
}
}
apply plugin: "com.felipefzdz.gradle.heroku"
N.B. Gradle 5.2 is required as minimum version.
This full-fledged plugin provides an opinionated view of some Heroku related tasks, plus the building blocks defined on the base plugin.
You’ll need an account in Heroku with its related API_KEY
. Creating a team is optional, as you can use personal apps. That key
can be injected into Gradle through an env key named GRADLE_HEROKU_PLUGIN_API_KEY
or a Gradle property named herokuPluginApiKey
.
-
GRADLE_HEROKU_PLUGIN_API_KEY=123 ./gradlew yourTask
-
./gradlew yourTask -PherokuPluginApiKey=123
import com.felipefzdz.gradle.heroku.tasks.model.HerokuWebApp
import com.felipefzdz.gradle.heroku.tasks.model.HerokuDatabaseApp
heroku {
bundles {
dev {
'database'(HerokuDatabaseApp) {
bundlePosition = 1
teamName = 'test'
stack = 'heroku-16'
personalApp = true
addons {
database {
plan = 'heroku-postgresql:hobby-dev'
config = ['version': '10.7']
waitUntilStarted = true
}
}
migrateCommand = 'bash'
}
'excludedDatabase'(HerokuDatabaseApp) {
excludeFromDeployBundle = true
teamName = 'test'
stack = 'heroku-16'
personalApp = true
addons {
database {
plan = 'heroku-postgresql:hobby-dev'
waitUntilStarted = true
}
}
migrateCommand = 'bash'
}
'web'(HerokuWebApp) {
bundlePosition = 2
teamName = 'test'
stack = 'heroku-16'
personalApp = true
addons {
'rabbitmq-bigwig' {
plan = 'rabbitmq-bigwig:pipkin'
waitUntilStarted = true
}
}
addonAttachments {
database {
owningApp = '$DATABASE_APP_NAME'
}
}
logDrains = ['logDrainUrl', 'anotherLogDrainUrl']
buildSource {
buildpackUrl = 'https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz'
buildUrl = { 'https://my-domain/example.tgz' }
buildVersion = '666'
}
config {
configToBeExpected = ['MODE': 'dev', 'API_KEY': 'secret']
}
features = ['http-session-affinity']
process {
type = 'web'
size = 'standard-1x'
quantity = 2
}
readinessProbe {
url = 'https://web.herokuapp.com/version'
command = { app, json ->
assert json.buildNumber == app.buildSource.buildVersion
}
}
disableAcm = true
domains = ['my-first.domain.com', 'my-second.domain.com']
}
}
}
}
The plugin defines the following tasks, being Dev
and Database
dynamic placeholders that will vary depending on your
bundle and app, respectively.
Task name | Type |
---|---|
|
|
|
|
|
|
|
The deploy bundle task will iterate over existing apps contained in the selected bundle and execute them in order (respecting exclusions, if any).
We can see here a list of steps that conforms the deployment of a web app:
maybeCreateApplication
installAddons
configureLogDrains
createBuild
addConfig
enableFeatures
addAddonAttachments
waitForAppFormation
updateProcessFormation
updateDomains
probeReadiness
maybeDisableAcm
Slightly different for a database app:
maybeCreateApplication
installAddons
configureLogDrains
createBuild
addConfig
waitForAppFormation
updateProcessFormation
migrateDatabase
maybeDisableAcm
This task might be executed as a pre step for a deployment, or as a infrastructure check to verify that the config contained in your gradle build (that represents your whole platform in a Disaster Recovery event) is consistent with the already deployed config.
To achieve that we provide four user generated collections plus the already deployed config. The task will fail if any of these
conditions hold: missingConfig || unexpectedConfig || incorrectConfig
As you shouldn’t store sensible config on your VCS (you should inject it in your env through your preferred mechanism), the convention
here is to fill those sensible values with secret
. Those values will be therefore excluded from the incorrectConfig
check.
This task also enforces the concept of cattle not pets, discouraging config changes made directly on the actual instances (unless you accompany that with a change in your Gradle build).
Based on the example DSL provided above:
./gradlew herokuDeployDevBundle
will deploy database
and web
apps on that order.
./gradlew herokuDeployDevDatabase
will just deploy database
.
./gradlew herokuDeployDevExcludedDatabase
will deploy excludedDatabase
as the exclusion only applies to the bundle task.
./gradlew herokuVerifyConfigForDevBundle
will verify that the config contained in the DSL for the dev
bundle is coherent with the already deployed config, if any.
./gradlew herokuVerifyConfigForDevDatabase
will verify that the config contained in the DSL for the database
app is coherent with the already deployed config, if any.
The heroku
closure expects the following property:
Property name | Type | Description |
---|---|---|
|
NamedDomainObjectContainer<HerokuAppContainer> |
A collection of bundles that serves as a wrapper for the apps to be deployed |
Within bundles
you should provide 1 to N named blocks that will represent your bundles, e.g. dev
and prod
.
Those HerokuAppContainer
will expect 1 to N HerokuApp
subtype blocks such as 'database'(HerokuDatabaseApp)
or 'web'(HerokuWebApp)
.
Let’s see the simple extension properties for HerokuApp
:
Property name | Type | Default value | Description |
---|---|---|---|
|
Integer |
-1 |
Instructs deployment order. This property is optional and in case of position collision first app defined will be first deployed. |
|
String |
'' |
Teams allow you to manage access to a shared group of applications and other resources. |
|
String |
'heroku-16' |
Stacks are the different application execution environments available in the Heroku platform. |
|
Boolean |
false |
Force creation of the app in the user account even if a default team is set. |
|
Boolean |
false |
Whether to destroy the app beforehand deploying it. |
|
List<String> |
[] |
Log drains provide a way to forward your Heroku logs to an external syslog server for long-term archiving. |
|
Boolean |
false |
Disable Automated Certificate Management (ACM) flag for an app. |
Those are the nested extension properties for HerokuApp
.
BuildSource
→ A build represents the process of transforming a code tarball into a slug.
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
'' |
Location of the buildpack for the app. Either a url (unofficial buildpacks) or an internal urn (heroku official buildpacks). |
|
Supplier<String> |
{ → '' } |
URL where gzipped tar archive of source code for build was downloaded. Supplier wrappers serves as a lazy mechanism to avoid costly calculations on configuration time, e.g. presigned AWS urls. |
|
String |
'' |
Version of the gzipped tarball. |
HerokuConfig
→ Allow you to manage the configuration information provided to an app on Heroku.
Property name | Type | Default value | Description |
---|---|---|---|
|
Map<String, String> |
[:] |
User defined config already deployed on an existing app |
|
List<String> |
[] |
User defined config to be removed on the next release |
|
List<String> |
[] |
User defined config to be added on the next release |
|
List<String> |
[] |
Heroku defined config such as DATABASE_URL |
HerokuAddon
→ Add-ons are cloud services that extend Heroku apps with useful features and services.
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
This is named container, so the value is mandatory when defining the DSL |
Globally unique name of the add-on pattern: ^[a-zA-Z][A-Za-z0-9_-]+$ |
|
String |
'' |
Unique name of this plan, e.g |
|
Man<String, String> |
[:] |
custom add-on provisioning options, e.g |
|
Boolean |
false |
If true, the task will wait up to 10 minutes (this hardcoded value will be made configurable in further plugin releases) with a dumb retry policy until the add-on has been added. |
HerokuProcess
→ The formation of processes that should be maintained for an app.
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
'' |
Type of process to maintain. Pattern: ^[-\w]{1,128}$, e.g |
|
Integer |
0 |
Number of processes to maintain. |
|
String |
'standard-1X' |
Dyno size. |
HerokuDatabaseApp
subtype offers the following extension properties:
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
'' |
Command to be executed as one-off dyno. This is meant to be used for db migration purposes, but you could hijack it for whatever you have in mind. Example: |
HerokuWebApp
subtype offers the following simple extension properties:
Property name | Type | Default value | Description |
---|---|---|---|
|
List<String> |
[] |
An app feature represents a Heroku labs capability that can be enabled or disabled for an app on Heroku, e.g. |
|
List<String> |
[] |
Domains define what web routes should be routed to an app on Heroku. |
HerokuWebApp
subtype offers the following nested extension properties.
HerokuAddonAttachment
→ An add-on attachment represents a connection between an app and an add-on that it has been given access to.
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
This is named container, so the value is mandatory when defining the DSL |
Unique name for this add-on attachment to this app. |
|
String |
'' |
Unique name of owning app. |
ReadinessProbe
→ Mechanism to verify that the app is up and running.
Property name | Type | Default value | Description |
---|---|---|---|
|
String |
'' |
Url for the health endpoint that will serve us as a readiness probe. |
|
BiAction<HerokuApp, Map<String, ?>> |
{ app, jsonResponse → } |
This function should assert onto the value returned by the health endpoint. Example: ` assert jsonResponse.buildId == app.buildSource.buildVersion` |
If you want to create your own opinionated view of a Heroku deployment, the base plugin will give you the required building blocks. You may also be interested on the building blocks in isolation to enhance with automation your existing deployment pipeline.
To use the base plugin, use these alternative snippets:
plugins {
id "com.felipefzdz.gradle.heroku.base" version "1.0.6"
}
Or using the buildscript block
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.felipefzdz.gradle.heroku:gradle-heroku-plugin:1.0.6"
}
}
apply plugin: "com.felipefzdz.gradle.heroku.base"
The base plugin provides the following custom task types:
Type | Description |
---|---|
An add-on attachment represents a connection between an app and an add-on that it has been given access to. |
|
Update config-vars for app. You can update existing config-vars by setting them again, and remove by setting it to null. |
|
Log drains provide a way to forward your Heroku logs to an external syslog server for long-term archiving. |
|
An app represents the program that you would like to deploy and run on Heroku. |
|
A build represents the process of transforming a code tarball into a slug |
|
A bundle is a collections of apps. |
|
Destroys an app. |
|
Destroys a bundle. |
|
An app feature represents a Heroku labs capability that can be enabled or disabled for an app on Heroku. |
|
Add-ons are cloud services that extend Heroku apps with useful features and services. |