Gradle Scaffolding is a Habitat package which helps you build your Gradle-based web applications, services, and processes (hereafter referred to as "apps") into a package which runs consistently on a wide range of containers, virtual machines, or servers via the Habitat Supervisor. The Supervisor will facilitate clustering, discovery of external services such as databases, dynamically update configuration and credentials, coordinate reliable rolling updates, and a lot more!
For more about Habitat, you can check out Try Habitat. For more about building your apps, read on!
Check out the Gradle Scaffolding QuickStart Guide.
To properly load and use this Scaffolding, the implementation looks for a build.gradle
or a settings.gradle
in your app's root directory. Either directory structure is supported:
A Plan in a habitat/
subdirectory:
.
├── build.gradle
├── settings.gradle
└── habitat
└── plan.sh
A Plan in the same directory as the build.gradle
or settings.gradle
:
.
├── build.gradle
├── settings.gradle
└── plan.sh
Either a build.gradle
or settings.gradle
must be present for this Scaffolding to function correctly.
Most non-trivial apps need more than their own codebase to run correctly. Many have library dependencies which may require compiling native code. Others require certain software present for shelling out to (for example: ImageMagick). If more packages than the defaults are required, it is generally easy to add these to your Plan to fully describe your app's build and runtime dependencies.
The following Habitat package dependencies will be injected into your app's Plan:
core/busybox-static
: Used by process bins to have valid shebangs and a consistent minimal command set. Will be injected into your Plan'spkg_deps
array.
Currently no additional Habitat packages are added based on the contents of the build.gradle
or the settings.gradle
files, although some may be added in the future.
Additional checks performed by this scaffolding are:
- The app's version of Gradle will be determined by checking the
plan.sh
. See the Gradle Version section for more details. - The app's version of Java runtime (i.e. JRE) will be determined by checking the
plan.sh
. See the JRE Version section for more details. - The app's version of Java compiler (i.e. JDK) will be determined by checking the
plan.sh
. See the JDK Version section for more details.
Generally speaking, your app needs additional Habitat packages present at runtime for one of two reasons:
- Your app expects certain programs present which it may shell out or bind to in some way.
- Your application has Java package dependencies with native extensions, requiring additional linked libraries.
In both cases, this means your app needs additional Habitat packages installed at runtime. For this, we use the pkg_deps
array in your Plan.
For this example, let's suppose your app resizes images to generate thumbnails by using the mogrify
program from the [ImageMagick][] software. To add this package, update your pkg_deps
line to the following below:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
pkg_deps=(core/imagemagick)
Finally, your app may require additional software present in order to build your app that is not present by default.
For this example, let's suppose you are using a fictional "MakeSmall" project to minify css files. For this you need the tool's program "mksm", but only at build time. To add the package, update your pkg_build_deps
line to the following below:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
pkg_build_deps=(core/makesmall)
By default the latest version of the [core/gradle
][] package will be injected into your Plan's pkg_build_deps
array. To specify a non-default version of Gradle, there is one location you can do this:
- Set the
scaffolding_gradle_pkg
variable in your Plan with a valid Habitat package identifier corresponding to a package with agradle
program
You can set the scaffolding_gradle_pkg
variable in your Plan to specify a version Gradle. For example:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
scaffolding_gradle_pkg=myown/gradle
The value of this variable will be used to determine the Habitat package to satisfy the role of your app's Gradle implementation.
By default the latest version of the [core/jdk8
][] package will be injected into your Plan's pkg_build_deps
array. To specify a non-default version of the JDK, there is one location you can do this:
- Set the
scaffolding_jdk_pkg
variable in your Plan with a valid Habitat package identifier corresponding to a package with ajavac
program
You can set the scaffolding_jdk_pkg
variable in your Plan to specify a version of the JDK. For example:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
scaffolding_jdk_pkg=myown/jdk
The value of this variable will be used to determine the Habitat package to satisfy the role of your app's JDK implementation.
By default the latest version of the [core/jre8
][] package will be injected into your Plan's pkg_deps
array. To specify a non-default version of the JRE, there is one location you can do this:
- Set the
scaffolding_jre_pkg
variable in your Plan with a valid Habitat package identifier corresponding to a package with ajava
program
You can set the scaffolding_jre_pkg
variable in your Plan to specify a version of the JRE. For example:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
scaffolding_jre_pkg=myown/jre
The value of this variable will be used to determine the Habitat package to satisfy the role of your app's JRE implementation.
Your app may have one or more top-level processes which map to a running service or ephemeral task. Each of these processes will be wrapped up in a small script which sets up a suitable app environment and invokes a command. By convention the main process bin which the package's run
hook will invoke is the web
process.
By default, two process bins will be generated: web
and sh
. The detection for web
is described in the Specifying Process Bins section and the sh
process is described immediately below.
Unless overridden, a web
process bin will be generated with the following:
web
:java -Dserver.port=$PORT $JAVA_OPTS -jar *.jar $EXTRA_OPTS
A bare-bones shell process bin will be generated for all apps. Due to the process bin wrapping logic, this shell session will have its $PATH
correctly set, all appropriate environment variables set and will be running in the app's installed root path.
sh
:sh
By default, the scaffolding will determine some of the process bins to be created. To customize your app's process bins, there are two locations you can do this:
- Use a
Procfile
in your app's root directory - Setup the
scaffolding_process_bins
hash in your Plan
For each process bin name a set Plan hash entry will win over a Procfile
entry, however it is recommended to use the Procfile
strategy first as this is portable across other Gradle app build and deployment solutions.
You can override default process bins or even add new ones by including a Procfile
in your app's root directory. By convention, the web
entry will be invoked by your package's run
hook and will therefore be considered your package's main service. Additional entries will generate additional process bins in your package. For example, let's take an app whose package name is set to "my_app"
(by setting pkg_name="my_app"
in your plan.sh
) and a Procfile containing:
web: java -Dserver.port=$PORT -jar myjar.jar
release: java -jar myjar.jar --migrate
The Scaffolding will produce my_app-web
and my_app-release
process bins in the resulting package under $pkg_prefix/bin
which will be in the package's $PATH
environment.
You can override default process bins or even add new ones by creating the scaffolding_process_bins
hash in your Plan and setting one or more entries. By convention, the web
entry will be invoked by your package's run
hook and will therefore be considered your package's main service. Additional entries will generate addition process bins in your package. For example, let's take an app whose package name is set to "my_app
" (by setting pkg_name="my_app"
in your plan.sh
):
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
# Declare the associative array (hash) in bash
declare -A scaffolding_process_bins
# Override the default web process
scaffolding_process_bins[web]='java -Dserver.port=$PORT -jar myjar.jar'
# Add an addition process bin called release
scaffolding_process_bins[release]='java -jar myjar.jar --migrate'
The scaffolding will produce my_app-web
and my_app-release
process bins in the resulting package under $pkg_prefix/bin
which will be in the package's $PATH
environment.
Note: future work may make the Bash associative array creation an easier task.
In order to run correctly, your app may require several environment variables set up which it would consume on start. At build time, a Plan hash called scaffolding_env
can be created to set up more app environment variables. The Scaffolding writes all of the app's environment variables to a config template which the Supervisor will compute and render at runtime. Each process bin will source the runtime-computed version of this config file before running itself meaning that all process bins have access to these variables.
The following default app environment variables are created:
PORT
:{{cfg.app.port}}
JAVA_OPTS
:${scaffolding_java_opts}
EXTRA_OPTS
:${scaffolding_extra_opts}
The values of these environment variables use handlebars templating, meaning that their values will be computed and rendered by the Supervisor at runtime.
To further customize your app's environment variables, you would setup the scaffolding_env
hash in your Plan.
For each environment variable name, a set Plan hash entry will win over a default entry.
You can override default app environment variables or even add new ones by creating the scaffolding_env
hash in your Plan and setting one or more entries. Let's see an example:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradlee
# Declare the associative array (hash) in bash
declare -A scaffolding_env
# Add an addition variable which is hardcoded at build time
scaffolding_env[MY_PKG_VERSION]="$pkg_version/$pkg_release"
# Add addition variables which is uses runtime config values
scaffolding_env[AWS_DEFAULT_REGION]="{{cfg.aws_default_region}}"
Note that in the above example we can choose to consume runtime configuration by writing the value with handlebars templating. This will be computed and rendered by the Supervisor at runtime and provided to your app. However, if the value is not "tunable" (i.e. would you change this setting in different environments or in production?), you can immutably set the variable by simply not using any handlebars templating syntax.
To make your app's package useful in more environments and contexts, your package will contain config settings. These settings will be computed at runtime and can be used to dynamically generate app config files, environment variable settings, etc.
The following config settings will be created for each app:
app.port
: Used to set the listen port number for your app when it runs. It is consumed to set the$PORT
environment variable and to set the value of the exportedport
configuration. This enforces the port binding contract. Note that your app must check for this environment variable for this contract to be fulfilled.
The Scaffolding creates some default app config settings, but to further customize your app's config settings, you would create a default.toml
file with your Plan.
You can add more config settings by creating a default.toml
file in the same directory containing your plan.sh
file. Either directory structure is supported:
A Plan in a habitat/
subdirectory:
.
├── build.gradle
├── settings.gradle
└── habitat
├── default.toml
└── plan.sh
A Plan in the same directory as the build.gradle
or the settings.gradle
file:
.
├── build.gradle
├── settings.gradle
├── default.toml
└── plan.sh
Service bindings are a powerful way to declare your app's service dependencies which the Supervisor will honor when running your app.
Currently, no service bindings are generated by default.
You can add service bindings in your Plan by setting an entry in the pkg_binds
hash. Let's see an example of declaring a required binding on an Elasticsearch service group:
pkg_name=my_app
pkg_origin=acmecorp
pkg_version=0.1.0
# ...
pkg_scaffolding=core/scaffolding-gradle
# We require both the HTTP and transport ports from this
# service binding
pkg_binds[elasticsearch]="http-port transport-port"
This allows your app to dynamically bind to the desired Elasticsearch service group at runtime. For example, if your app's target Elasticsearch service group was "es.my_app"
, then you would start it with:
hab start acmecorp/my_app --bind elasticsearch:es.my_app
Several popular Java-based frameworks are detected are supported. See below for details on the state of each app type.
Spring Boot app type will be detected if the app's build.gradle
contains an org.springframework.boot.:spring-boot
artifact.
This app type will be built using a variant of gradle build -x test
.
Ratpack app type will be detected if the app's build.gradle
contains an io.ratpack.ratpack
artifact.
This app type will be built using a variant of gradle installDist -x test
.