This is a suite of plugins for packaging and publishing Helm Charts from Gradle, and managing Helm releases on a Kubernetes cluster.
Prerequisites
You need at least the following:
-
Gradle 5.2 or higher
-
JDK 8 or higher (for running Gradle)
-
Helm CLI (3.+)
This plugin delegates all
helm
commands to a locally installed Helm CLI. See Installing Helm in the Helm documentation for installation instructions on various systems.Starting with version 0.5.0, the plugin requires Helm 3. However, the chart API version
v1
is still supported by Helm 3, and also by the plugin. (The main difference betweenv1
andv2
is thatv1
declares chart dependencies in a file calledrequirements.yaml
, whereas inv2
the dependencies are declared directly in theChart.yaml
file.)INFO: Delegating to the Helm CLI decouples the plugin release cycle from the Helm release cycle, but it also means that some features offered by the plugin may not be available in the CLI and vice versa.
Quick Start
Apply the com.citi.helm
plugin to your Gradle project:
plugins {
id 'com.citi.helm' version '2.2.0'
}
plugins {
id("com.citi.helm") version "2.2.0"
}
Put your Helm chart sources into src/main/helm
:
📂 (project root) 📂 src 📂 main 📂 helm 📂 templates 📄 ... 📄 Chart.yaml 📄 values.yaml
Use the helmPackage
task to build your chart.
Plugins
The gradle-helm-plugin library provides several Gradle plugins, with different use cases for each.
All plugins IDs are prefixed the namespace com.citi
.
helm-commands
This plugin does little more than add the plugin library to your build’s classpath so you can define tasks for
various Helm CLI commands. It creates the helm
DSL block which you can use to configure common settings
for all tasks, like the Helm home path and the Kubecontext. Apart from this, it does not create any tasks
automatically.
Use this if you want to use Helm CLI commands directly as tasks, without any of the declarative DSL that the helm
plugin offers.
plugins {
id 'com.citi.helm-commands' version '2.2.0'
}
plugins {
id("com.citi.helm-commands") version "2.2.0"
}
All the other plugins imply helm-commands
, so you don’t need to apply this plugin if you use any of the others.
helm
This is the main plugin of the suite. It enables a variety of extra DSL blocks inside helm
, for example charts
and repositories
.
plugins {
id 'com.citi.helm' version '2.2.0'
}
plugins {
id("com.citi.helm") version "2.2.0"
}
helm-publish
Use this plugin to publish charts to a remote chart repository like ChartMuseum.
It enables the helm.publishing
DSL block and adds publishing tasks for each chart.
plugins {
id 'com.citi.helm-publish' version '2.2.0'
}
plugins {
id("com.citi.helm-publish") version "2.2.0"
}
helm-releases
Use this plugin to manage Helm releases (install/upgrade/uninstall) on a remote Kubernetes cluster.
It enables the helm.releases
DSL block, where you can declare your releases using Gradle DSL, referencing either
Helm charts built using the other projects, or existing charts from a Helm repository.
plugins {
id 'com.citi.helm-releases' version '2.2.0'
}
plugins {
id("com.citi.helm-releases") version "2.2.0"
}
Configuring the Helm Client
The global flags for Helm can be configured directly in the helm
DSL block.
Most of these correspond to options that are passed to every Helm CLI command as a parameter or an environment variable.
As these settings can be dependent on the build environment, it is often useful to specify them in
|
Global Helm Options
The following properties can be set on the helm
extension in the Gradle build script.
Property | Description | helm CLI option |
---|---|---|
|
Path to the |
|
|
Additional arguments to pass to every invocation of |
|
|
Path to the Kubernetes configuration file. |
Environment variable |
|
Name of the kubeconfig context to use. |
|
|
Kubernetes namespace scope. |
|
|
Time to wait for any individual Kubernetes operation (like Jobs for hooks). |
|
The environment variable In general, Gradle builds should avoid depending on environment variables because it makes the build less predictable and reproducible, and may behave unexpectedly when the Gradle daemon is used. |
Helm Directories
Starting with Helm 3, Helm does not have a "helm home" directory anymore, and dropped the need for a call to
helm init
before working with Helm. Instead, local Helm directories are governed by 3 environment variables,
which can be configured in the Gradle helm
extension. By default, these directories are set to paths inside
the Gradle project, so that any helm
invocation from the Gradle build is independent from the Helm configuration
present on the local machine.
It is also possible to set these properties via gradle.properties
or pass them using the -P
flag on the
Gradle command line (see Configuration from Project Properties).
See the Helm docs for details about how XDG base directories are used by the Helm CLI.
helm extension property |
Description | Environment variable | Default Value |
---|---|---|---|
|
Base directory for storing data. (Note: this does not currently seem to be used by any |
|
|
|
Base directory for storing configuration. |
|
|
|
Base directory for storing cached data. |
|
|
The cache home, as indicated by + If necessary, you can always set the cache home to a project-local directory, for example: Groovy
Kotlin
|
Automatic Download of the Helm Client
As an alternative to using a locally installed Helm client, the plugin can automatically download a Helm client distribution from the official Helm website. This allows build scripts to be more independent from the environment found on the local machine where the Gradle build runs.
The download can be configured either through the helm.downloadClient
DSL block or through project properties (e.g.
in the gradle.properties
file). Project properties have the advantage of being automatically inherited by subprojects,
and are easier to adapt for different build environments.
The following example shows how the download can be configured in the build script:
helm {
downloadClient {
enabled = true
version = '3.4.1'
}
}
helm {
downloadClient {
enabled.set(true)
version.set("3.4.1")
}
}
The following example shows how the download can be configured using the gradle.properties file:
helm.client.download.enabled=true
helm.client.download.version=3.4.1
Using with Multi-Project Builds
When using the Helm plugin in a multi-project build, the tasks to download and extract the Helm client are installed only once on the root project, in order to avoid multiple downloads of the same client package.
While it is possible that different subprojects use different versions of Helm, some of the properties that control
the download behavior (e.g. the URL) can only be configured globally in the root project’s gradle.properties
file.
It is recommended that these settings are configured entirely by the root project gradle.properties
, so that the
same settings will automatically be used for all subprojects that use Helm.
Helm Client Download Property Reference
The following properties control the download of the Helm client:
DSL property under helm.downloadClient |
Project property | Description | Default Value |
---|---|---|---|
|
|
If set to |
|
|
|
The version of the Helm client to be downloaded. |
The latest version of Helm available at the time the plugin is published (currently |
|
The base URL for downloading the client executables. You can change this to a different URL if required, for example when behind a corporate proxy. Note that this property is considered "global" for multi-project builds, and has to be specified on the root project. |
||
|
Override the OS classifier (the suffix of the downloaded filename) if auto-detection does not work as expected. |
Auto-detected for the current system based on Java system properties. Note that this property is considered "global" for multi-project builds, and has to be specified on the root project. For example, |
Defining Charts
With the helm
plugin you can configure your Helm chart using a declarative DSL, and the corresponding Gradle
tasks will be added automatically.
Groovy
plugins {
id 'com.citi.helm' version '2.2.0'
}
helm {
charts {
foo {
chartName = 'foo'
chartVersion = '1.2.3'
sourceDir = file('src/helm')
}
}
}
Kotlin
plugins {
id("com.citi.helm") version "2.2.0"
}
helm {
charts {
create("foo") {
chartName.set("foo")
chartVersion.set("1.2.3")
sourceDir.set(file("src/helm"))
}
}
}
Note that the chart moniker used in the DSL and the actual chart name are not necessarily the same, unless you set them to the same value.
The following properties can be configured on a chart:
Property | Description | Default |
---|---|---|
|
The name of the chart, as used in various |
Defaults to the name of the chart in the DSL. |
|
The version of the chart, as used by |
Defaults to the Gradle project version. |
|
The directory containing the chart sources ( |
Defaults to |
|
An optional Gradle |
By default, no files are copied. |
Helm Chart Tasks
From the above chart definition, a number of tasks will be created automatically:
- Task
helmFilterFooChartSources
-
Resolves placeholders in the chart sources.
- Task
helmCollectFooChartDependencies
-
Resolves chart dependencies within the project (see Managing Chart Dependencies).
- Task
helmCollectFooChartSources
-
Collects all the sources for a chart (including dependencies and extra files) into one directory.
- Task
helmUpdateFooChartDependencies
-
Equivalent to the
helm dependency update
CLI command. - Task
helmLintFooChart
-
Equivalent to the
helm lint
CLI command. See Linting Charts for details on configuring the linting step. - Task
helmPackageFooChart
-
Equivalent to the
helm package
CLI command.
In addition, the plugin creates a task named helmPackage
that will depend on all charts' package task, so it can be
used to build all the project’s charts. Most of the time, you will just call helmPackage
to build your charts.
Helm Chart Configurations
The following configurations and artifacts will be created for each chart:
- Configuration
helmFoo
-
Contains a single artifact that has the chart directory as its output, and is built by the
helmBuildFooChartDependencies
task. - Configuration
helmFooPackaged
-
Contains a single artifact that has the packaged (tar.gz) chart file as its output, and is built by the
helmPackageFooChart
task.
Using the main
chart
For the typical case of building a single Helm chart in a project, the Helm plugin provides some useful conventions so that a single "main" chart can be built with a minimum of configuration.
If you don’t define any charts in the helm.charts
DSL block, and your project contains a file
src/main/helm/Chart.yaml
, then by convention a chart named main
will be
defined automatically, equivalent to the following:
helm.charts {
main {
sourceDir = file('src/main/helm')
}
}
helm.charts {
create("main") {
sourceDir.set(file("src/main/helm"))
}
}
since Helm chart versions must be SemVer-compliant, you should either make sure that the project version is a valid SemVer, or set the main chart version to a different value. |
The main
chart will not be instantiated if you define any other charts; however you can create the main
chart
explicitly if you would like to use it anyway:
helm.charts.main.chartVersion = '1.0.0'
helm.charts.create("main") {
chartVersion.set("1.0.0")
}
Some IDEs (e.g. IntelliJ IDEA) offer support for Helm, and will report a warning or error if the |
Using Charts in a Multi-Project Build
Of course, instead of defining multiple charts in one Gradle project, you can also have a multi-project build where
each subproject defines a single main
chart. That way, you can take advantage of the main
chart convention in
every project.
However, note that the values defined in the helm
block are not automatically inherited by subprojects.
If you want to define your Helm CLI options in one central place, you can add a subprojects
clause in the root
project:
subprojects {
// Use verbose logging on all Helm commands
helm.extraArgs.addAll("-v", "1")
}
subprojects {
// Use verbose logging on all Helm commands
helm.extraArgs.addAll("-v", "1")
}
Adding Extra Files to a Chart
Each chart defined in the DSL has an extraFiles
property which is a Gradle CopySpec
. It allows you to copy
additional files into the chart when it is built.
helm.charts {
myChart {
sourceDir = file 'src/helm/my-chart'
extraFiles {
from('src/extra/script.js') {
into('files/scripts')
}
}
}
}
helm.charts {
create("myChart") {
sourceDir.set(file("src/helm/my-chart"))
extraFiles {
from("src/extra/script.js") {
into("files/scripts")
}
}
}
}
In most cases, the source files of the chart should be put into the chart source directory.
The extraFiles
mechanism is primarily designed for cases where some parts of the chart need to be dynamically
generated during the Gradle build.
If the extraFiles copy spec refers to a RegularFileProperty or DirectoryProperty that represents the output
of a task, or to an artifact declared by another project, a task dependency will automatically be set up so that the
task building the file is run before the Helm chart is built.
|
Defining Repositories
Chart repositories are external storages of Helm charts.
You can use the helm.repositories
block to define repositories:
helm {
repositories {
example {
url 'http://helm-repo.example.com/'
}
}
}
helm {
repositories {
create("example") {
url("http://helm-repo.example.com/")
}
}
}
Defining a repository will automatically add a task helmAdd<X>Repository
(for example,
helmAddExampleRepository
) that does the equivalent of helm repo add
.
For convenience, some shorthand methods are defined for registration of some well-known repositories:
helm {
repositories {
// registers the Helm stable repo under the name "stable"
helmStable()
// registers the Helm incubator repo under the name "incubator"
helmIncubator()
// registers the Bitnami repo under the name "bitnami"
bitnami()
}
}
helm {
repositories {
// registers the Helm stable repo under the name "stable"
helmStable()
// registers the Helm incubator repo under the name "incubator"
helmIncubator()
// registers the Bitnami repo under the name "bitnami"
bitnami()
}
}
Configuring Repository Credentials
If a chart repository requires authentication, you can configure credentials using the credentials
block. It works very similarly to other places in Gradle where credentials are used.
helm {
repositories {
example {
url 'http://helm-repo.example.com/'
credentials {
username = 'user'
password = 'password'
}
}
}
}
helm {
repositories {
create("example") {
url("http://helm-repo.example.com/")
credentials {
username.set("user")
password.set("password")
}
}
}
}
In addition to username/password credentials, certificate credentials based on PEM files are also supported:
import com.citi.gradle.plugins.helm.dsl.credentials.CertificateCredentials
helm {
repositories {
example {
url 'http://helm-repo.example.com/'
credentials(CertificateCredentials) {
certificateFile = file 'path/to/cert.pem'
keyFile = file 'path/to/key.pem'
}
}
}
}
import com.citi.gradle.plugins.helm.dsl.credentials.CertificateCredentials
helm {
repositories {
create("example") {
url("http://helm-repo.example.com/")
credentials(CertificateCredentials::class.java) {
certificateFile.set(file("path/to/cert.pem"))
keyFile.set(file("path/to/key.pem"))
}
}
}
}
Filtering Chart Sources
The plugin includes a filtering step that will resolve placeholders in certain chart files before the chart is packaged.
Filtered files are processed using a Groovy
SimpleTemplateEngine. Filter
expressions are Groovy expressions enclosed in ${
and }
, for example ${chartName}
.
If you have actual $ characters in these files, you can escape them as \$ so they are not interpreted as a filter expression.
|
The following placeholders are automatically available based on the corresponding properties of the chart in
the DSL. You can also define additional placeholders and their values using the filtering.values
property (see below).
Placeholder |
Content |
|
The chart name, as set by the |
|
The chart version, as set by the |
|
The |
The name and version in the Chart.yaml are automatically replaced based on the chart information from the
Gradle script. You do not need to add filter expressions for these.
|
Enable or Disable Filtering
The filtering is enabled by default. You can set the filtering.enabled
property to false
if you would not like
the chart to be filtered. This can be done either on the project level or the chart level:
helm {
// Disable filtering by default
filtering.enabled = false
charts {
chart1 {
// filtering will be enabled for this chart
filtering.enabled = true
}
chart2 {
// filtering will be disabled for this chart (inheriting settings from project level)
}
}
}
helm {
// Disable filtering by default
filtering.enabled.set(false)
charts {
create("chart1") {
// filtering will be enabled for this chart
filtering.enabled.set(true)
}
create("chart2") {
// filtering will be disabled for this chart (inheriting settings from project level)
}
}
}
Configure Which Files are Filtered
By default, filtering applies only to the Chart.yaml
, requirements.yaml
and values.yaml
files directly
in the chart directory, but not to any templates or other files in the chart.
If templates in a chart need to be dynamically modified based on the Gradle script, consider adding a value
to the values.yaml file that uses a filtering expression, and then use that value in your templates using
Helm’s templating constructs.
|
The filtering
block has a filePatterns
property which holds the (Ant-style) patterns of files to be filtered.
If this list is empty, it is equivalent to the pattern *
so all files in the chart will be filtered.
helm {
filtering {
// apply filtering to all files in the chart
filePatterns.add('*')
}
}
helm {
filtering {
// apply filtering to all files in the chart
filePatterns.add("*")
}
}
Supply Additional Values
Use the filtering.values
property to define additional values to be available in filtering expressions.
Again, values may be specified in the project-level filtering
block, as well as on a filtering
block for an
individual chart. Project-level and chart-level values are merged, with the chart-level values having precedence
if a value is defined in both places.
helm {
filtering {
// This value will be resolvable in all charts' YAML files as ${authorName}
values.put 'authorName', 'A. U. Thor'
}
(charts) {
"main" {
filtering {
// add a custom value that can be resolved only inside this chart's YAML files.
values.put 'dockerImage', 'busybox'
}
}
}
}
helm {
filtering {
// This value will be resolvable in all charts' YAML files as ${authorName}
values.put("authorName", "A. U. Thor")
}
charts {
main {
filtering {
// add a custom value that can be resolved only inside this chart's YAML files.
values.put("dockerImage", "busybox")
}
}
}
}
You can also pass a Provider or Property as the value; in this case it will be resolved lazily when the filtering
task runs.
|
Example: Pass the JIB Docker Image Name and Version
Many Helm charts use the following conventional structure in their values.yaml
to specify the Docker image to be
used in Kubernetes deployments:
image:
repository: "gcr.io/awesome-project/awesome-image"
tag: "1.2.3"
If the Docker image is built with JIB in the same project, we can avoid the repetition (which is bound to cause silly copy & paste errors eventually) and pass the values from Gradle instead:
helm {
filtering {
values.put 'imageRepository', jib.to.image
values.put 'imageTag', jib.to.tags.first()
}
}
helm {
filtering {
values.put("imageRepository", jib.to.image)
values.put("imageTag", jib.to.tags.first())
}
}
And then refer to these values in the values.yaml
file:
image:
repository: ${imageRepository}
tag: ${imageTag}
Values from Files
Values can also be read from the contents of files; use the filtering.fileValues
property for this. The following
example would generate a RSA private key using openssl
and then pass it to the filtering:
def rsaKeyFile = objects.file()
.fileValue(file("$buildDir/rsa.key"))
task generateRsaKey(type: Exec) {
// Declare the file as a task output so that task dependencies are set up by Gradle
outputs.file rsaKeyFile
executable = 'openssl'
arg 'genrsa', '-out', rsaKeyFile.get()
}
helm.filtering {
fileValues.put 'rsaKey', rsaKeyFile
}
val rsaKeyFile = objects.file()
.fileValue(file("$buildDir/rsa.key"))
tasks.create("generateRsaKey", Exec::class) {
// Declare the file as a task output so that task dependencies are set up by Gradle
outputs.file(rsaKeyFile)
executable = "openssl"
arg("genrsa", "-out", rsaKeyFile.get())
}
helm.filtering {
fileValues.put("rsaKey", rsaKeyFile)
}
Afterwards, we can use this value in our values.yaml
file (note that the value contains newlines, so we need
to do some processing for correct YAML indentation):
rsaKey: |
${ rsaKey.collectReplacements { it == '\n' ? '\n ' : null } }
Linting Charts
The default workflow for building and packaging a Helm chart includes a lint step, which is
performed by the HelmLint
task and maps to a helm lint
CLI call.
Both the global helm
DSL block and each chart
block provide a nested lint
block, where you
can fine-tune linting on a global or chart level. Each chart will inherit the global configuration
and can selectively override certain aspects of it.
helm {
// The global lint configuration applies to all charts
lint {
// treat linter warnings as errors (failing the build)
strict = true
// also lint dependent charts
withSubcharts = true
}
charts {
main {
// This configures linting only for the main chart
lint {
// disable strict linting only for this chart
strict = false
}
}
}
}
helm {
// The global lint configuration applies to all charts
lint {
// treat linter warnings as errors (failing the build)
strict.set(true)
// also lint dependent charts
withSubcharts.set(true)
}
charts {
create("main") {
// This configures linting only for the main chart
lint {
// disable strict linting only for this chart
strict.set(false)
}
}
}
}
Passing Values to the Linter
You can pass values to the linter using the values
, fileValues
and valueFiles
.
-
values
contains values to be passed directly (CLI option:--set
or--set-string
) -
fileValues
contains values to be read from files (CLI option:--set-file
) -
valueFiles
contains a list of files that contain values in YAML format (CLI option:--values
)
Passing values to helm lint
may be necessary to avoid warnings if you use the required
function in your Helm
templates (see Helm issue #2347). Also, it can be useful if
you have conditional templates which would not be rendered when using the default values
from values.yaml.
helm lint does not actually perform a syntactic analysis of your charts; instead it renders
the template internally (just as helm template would do) and checks the resulting YAML for
correctness.
|
Values and value files defined in the global lint
block are automatically inherited by
each chart; however a chart can add additional values or value files.
helm {
lint {
valueFiles.from 'src/test/helm/helm-lint-global.yaml'
}
charts {
main {
lint {
valueFiles.from 'src/test/helm/helm-lint-main.yaml'
values.put 'foo', 'bar'
}
}
}
}
helm {
lint {
valueFiles.from("src/test/helm/helm-lint-global.yaml")
}
(charts) {
"main" {
lint {
valueFiles.from("src/test/helm/helm-lint-main.yaml")
values.put("foo", "bar")
}
}
}
}
Disable the Lint Step
You can disable the lint step altogether by setting the lint.enabled
property to false
. This
works on a global as well as on a chart level. (Again, the chart setting is inherited from the
global setting).
helm {
lint {
// disable linting by default
enabled = false
}
charts {
main {
// enable linting for the main chart
lint.enabled = true
}
}
}
helm {
lint {
// disable linting by default
enabled.set(false)
}
(charts) {
"main" {
// enable linting for the main chart
lint.enabled.set(true)
}
}
}
Using Multiple Lint Configurations
Many charts use conditional expressions like {{ if }} … {{ else }}
to include parts of a template based on the
supplied values. This can make it difficult to completely lint the chart in one go, because often there is
no combination of values that can be supplied to the linter that would really render everything in the chart.
The plugin addresses this by supporting multiple linter configurations: Inside the lint
block for a chart,
you have a container for configurations
, and each configuration can have its own values
, fileValues
and
valueFiles
. Each lint configuration will give rise to a separate HelmLint
task that will run the linter with
the specified input.
The values
, fileValues
and valueFiles
for each lint configuration are merged with (and can selectively
override) the corresponding properties from the chart’s lint
block and the global lint
block, allowing you to
have a hierarchy of common and more specialized values.
helm {
lint {
// This values file will be used for ALL lint configurations in ALL charts
valueFiles.from 'src/test/helm/helm-lint-all.yaml'
}
charts {
main {
lint {
// This values file will be used for ALL lint configurations in this chart
valueFiles.from 'src/test/helm/helm-lint-main-common.yaml'
configurations {
basic {
// This values file will be used for the basic configuration only
valueFiles.from 'src/test/helm/helm-lint-main-basic.yaml'
}
advanced {
// This value file will be used for the advanced configuration only
valueFiles.from 'src/test/helm/helm-lint-main-advanced.yaml'
}
}
}
}
}
}
helm {
lint {
// This value file will be used for ALL lint configurations in ALL charts
valueFiles.from("src/test/helm/helm-lint-all.yaml")
}
charts {
"main" {
lint {
// This value file will be used for ALL lint configurations in this chart
valueFiles.from("src/test/helm/helm-lint-main-common.yaml")
configurations {
register("basic") {
// This value file will be used for the basic configuration only
valueFiles.from("src/test/helm/helm-lint-main-basic.yaml")
}
register("advanced") {
// This value file will be used for the advanced configuration only
valueFiles.from("src/test/helm/helm-lint-main-advanced.yaml")
}
}
}
}
}
}
Now, running helmLint<Chart>Chart
(or other tasks that depend on it, such as helmPackage
) will invoke helm lint
for each configuration, by way of a separate HelmLint
task per configuration that it depends on.
You can also run the linter for a single configuration using the helmLint<Chart>Chart<Configuration>
.
If you do not add any lint configurations, the plugin automatically creates a single configuration named "default" with no additional values. |
Managing Chart Dependencies
The helm
plugin can manage dependencies between charts in the same Gradle build.
This is especially useful if you have multiple charts in the same project (or in a multi-project build) that depend on one another, because it will also add Gradle task dependencies to make sure that the charts are packaged in the correct order.
Chart dependencies are resolved by packaging the dependency chart and then extracting it into the charts
sub-directory of the dependent chart. This does not involve any of the standard Helm dependency mechanisms
(helm dependency build
/ helm dependency update
) and will run before helm dependency update
.
Declaring a Dependency
To declare a dependency from one chart onto another, use the dependencies
block inside a chart
.
The following example defines two charts foo
and bar
, and declares a dependency from bar
onto foo
:
helm.charts {
foo {
chartVersion = '1.2.3'
sourceDir = file 'src/helm/foo'
}
bar {
chartVersion = '3.2.1'
sourceDir = file 'src/helm/bar'
dependencies.add 'foo'
}
}
helm.charts {
create("foo") {
chartVersion.set("1.2.3")
sourceDir.set(file("src/helm/foo"))
}
create("bar") {
chartVersion.set("3.2.1")
sourceDir.set(file("src/helm/bar"))
dependencies.add("foo")
}
}
Instead of using the name, it is also possible to refer directly to the HelmChart
DSL object in the dependency:
helm.charts {
foo {
chartVersion = '1.2.3'
sourceDir = file 'src/helm/foo'
}
bar {
chartVersion = '3.2.1'
sourceDir = file 'src/helm/bar'
dependencies.add foo
}
}
(helm.charts) {
val foo by creating {
chartVersion.set("1.2.3")
sourceDir.set(file("src/helm/foo"))
}
val bar by creating {
chartVersion.set("3.2.1")
sourceDir.set(file("src/helm/bar"))
dependencies.add(foo)
}
}
Configuring the Dependency in Chart.yaml
Since the dependency resolution done by this plugin is simply a package-and-extract operation, it is not necessary
to mention the dependency in your Chart.yaml
file (or requirements.yaml
for API version v1
). However, for some
advanced configuration of the dependency (e.g. when using an alias
or import-values
), you can still declare it.
In that case, you should omit the repository
so that helm dependency update
will not attempt to resolve it from
a chart repository:
apiVersion: v2
name: bar
version: 3.2.1
dependencies:
- name: foo
alias: fooAlias
import-values:
- fooData
When running helm dependency update
(via the helmUpdateBarDependencies
task), it will output something like:
Dependency foo did not declare a repository. Assuming it exists in the charts directory
Chart Dependencies in Multi-Project Builds
If you have a Gradle multi-project build, chart dependencies can also refer to charts built by other projects.
Use the project
parameter when adding a dependency, in addition to the chart
parameter:
dependencies {
// Assuming that the foo chart is defined in the foo project
add project: ':foo', chart: 'foo'
}
dependencies {
// Assuming that the foo chart is defined in the foo project
add(project = ":foo", chart = "foo")
}
The chart
parameter defaults to "main"
, so if a dependency references another project’s main
chart, the chart
parameter can be omitted:
dependencies {
// Referencing the main chart in the foo project
add project: ':foo'
}
dependencies {
// Referencing the main chart in the foo project
add(project = ":foo")
}
Mixing Gradle and Helm Dependency Resolution
A chart may have external dependencies (to be resolved by helm dependency update
) in addition to the dependencies
managed by the Gradle plugin.
At the point when helm dependency update
is run, the resolved charts from Gradle will already be present in the
charts/
sub-directory, and helm dependency update
should be smart enough to ignore them.
Resolving External Chart Dependencies
The dependency resolution mechanism described here is primarily intended for resolving dependencies on charts in the
same Gradle project, or in another Gradle project that is part of the same multi-project build. For dependencies on
external charts, it is best to publish those to a chart repository
(for example, using the helm-publish
plugin)
and then use Helm’s standard dependency mechanism to resolve them.
However, in some cases you might prefer to use Gradle to resolve those dependencies. For example, if you already have a Maven repository for your internal artifacts, and only a few common charts to be used as dependencies, it might be more convenient to publish those charts to your Maven repository instead.
The Helm plugin internally creates a dependency configuration called helm<Chart>Dependencies
, for example
helmFooDependencies
for a chart named foo
. You can use Gradle’s standard dependencies
block to add external
module dependencies to this configuration. It is required that these resolve to a single tar.gz compressed file,
like the one that is produced by helm package
.
helm.charts {
foo {
sourceDir = file 'src/helm/foo'
}
}
// Note: this is the Gradle project dependencies block, not the chart's
dependencies {
// Assuming that a packaged Helm chart is available at these GAV coordinates
helmFooDependencies 'com.example.bar:bar-helm-chart:3.2.1@tgz'
}
helm.charts {
create("foo") {
sourceDir.set(file("src/helm/foo"))
}
}
// Note: this is the Gradle project dependencies block, not the chart's
dependencies {
// Assuming that a packaged Helm chart is available at these GAV coordinates
"helmFooDependencies"("com.example.bar:bar-helm-chart:3.2.1@tgz")
}
Rendering Templates Locally
The helm
plugin allows you to locally render a Helm chart that is defined in your
build script by calling helm template
.
Simple Usage
When you declare a chart in your build script using the helm.charts
construct, the
plugin will register a task for each chart named helmRender<Name>Chart
to invoke
helm template
on the packaged chart. The rendered manifests will be placed in
helm/render/<chart>/default/
beneath the project’s build directory.
For example, given the following build script:
helm.charts {
awesome {
// ... configure the chart
}
}
helm.charts {
create("awesome") {
// ... configure the chart
}
}
Then there will be a task named helmRenderAwesomeChart
that
will render the templates of the chart into build/helm/render/awesome/default
.
The helmRender
task will render all charts in the
project. This can be useful if you have multiple charts defined in the project, or
simply for the convenience of a short task name.
Configure Renderings
Each chart has a renderings
container where you can fine-tune the renderings.
The options of a rendering correspond with the arguments to the helm template
invocation.
A rendering named default
will automatically be available for each chart (if you don’t
create one named default
yourself), and it starts out with a basic configuration.
You can adjust the default
rendering in your script, for example to add some values:
helm.charts {
main {
renderings {
'default' {
releaseName = 'my-release'
// Kubernetes API versions used for Capabilities.APIVersions
// (helm template --api-versions)
apiVersions.addAll('v1', 'apps/v1', 'batch/v1')
// set .Release.IsUpgrade instead of .Release.IsInstall
// (helm template --is-upgrade)
isUpgrade = true
// only show manifests rendered from the given templates
// (helm template --show-only)
showOnly.addAll('deployment.yaml', 'service.yaml')
// use release name in the output-dir path
// (helm template --release-name)
useReleaseNameInOutputPath = true
// validate manifests against the Kubernetes cluster
// (helm template --validate)
validate = true
values.put('foo', 'bar')
}
}
}
}
helm.charts {
create("main") {
renderings {
// Note: you can use create, register or named -- the default rendering
// will be created automatically if it does not exist
named("default") {
releaseName.set("my-release")
// Kubernetes API versions used for Capabilities.APIVersions
// (helm template --api-versions)
apiVersions.addAll("v1", "apps/v1", "batch/v1")
// set .Release.IsUpgrade instead of .Release.IsInstall
// (helm template --is-upgrade)
isUpgrade.set(true)
// only show manifests rendered from the given templates
// (helm template --show-only)
showOnly.addAll("deployment.yaml", "service.yaml")
// use release name in the output-dir path
// (helm template --release-name)
useReleaseNameInOutputPath.set(true)
// validate manifests against the Kubernetes cluster
// (helm template --validate)
validate.set(true)
values.put("foo", "bar")
}
}
}
}
Additional renderings can be added, for example to supply a different set of values to catch all conditional branches in your template logic:
helm.charts {
main {
renderings {
withExistingSecret {
values.put('existingSecret', 'secretName')
}
}
}
}
helm.charts {
create("main") {
renderings {
create("withExistingSecret") {
values.put("existingSecret", "secretName")
}
}
}
}
The following tasks will execute the renderings:
-
Task
helmRenderMainChart<Name>Rendering
for a specific rendering. In the above example, run thehelmRenderMainChartWithExistingSecretRendering
task to execute thewithExistingSecret
rendering. -
Task
helmRenderMainChart
will execute all renderings of themain
chart (including thedefault
rendering). -
Task
helmRender
will execute all renderings of all charts.
Rendering Output
The output for each rendering (i.e. the rendered manifests) will be placed in the
build/helm/render/main/<name>
directory. You can access the output directory with
the outputDir
property of each rendering, for example to configure other tasks
that perform further work on the output of helm template
.
Publishing Charts
The helm-publish
plugin allows you to publish your charts to remote repositories over HTTP.
There is currently no "official" API to publish Helm charts; Helm defines only how charts should be served from a repository. The plugin directly supports several types of popular repository servers, including ChartMuseum, Artifactory, Harbor, Nexus and Gitlab. For other repository types that are not directly supported, you could try using the |
Apply the helm-publish
plugin to your project:
plugins {
id 'com.citi.helm-publish' version '2.2.0'
}
plugins {
id("com.citi.helm-publish") version "2.2.0"
}
The plugin adds another sub-extension helm.publishing
that lets you define the repository or
repositories to publish to:
helm {
publishing {
repositories {
chartMuseum('example') {
url = uri('http://helm-repo.example.com/')
}
artifactory('myRepo') {
url = uri('http://artifactory.example.com/helm-local')
}
}
}
}
helm {
publishing {
repositories {
chartMuseum("example") {
url.set(uri("http://helm-repo.example.com/"))
}
artifactory("myRepo") {
url.set(uri("http://artifactory.example.com/helm-local"))
}
}
}
}
This will automatically define some Gradle tasks in the project:
- Task
helmPublish
-
Publishes all charts to all repositories.
- Task
helmPublish<X>Chart
-
Publishes chart X to all repositories.
- Task
helmPublish<X>ChartTo<Y>Repo
-
Publishes chart X to repository Y, e.g.
helmPublishMainChartToExampleRepo
.
There is no connection between the repositories in If you want to download from and publish to
the same external repository, you would need to specify it both in |
If you only define a single publishing repository, the name can be omitted, in which case the name "default"
is
used:
helm {
publishing {
repositories {
artifactory {
url = uri('http://artifactory.example.com/helm-local')
}
}
}
}
helm {
publishing {
repositories {
artifactory {
url.set(uri("http://artifactory.example.com/helm-local"))
}
}
}
}
Repository Types
The following repository types are supported:
-
artifactory
-
chartMuseum
-
harbor
-
nexus
-
gitlab
-
custom
ChartMuseum Repositories
For ChartMuseum, simply use the chartMuseum
repository type and configure the URL:
helm {
publishing {
repositories {
chartMuseum {
url = uri('https://chartmuseum.example.com')
}
}
}
}
helm {
publishing {
repositories {
chartMuseum {
url.set(uri("http://chartmuseum.example.com"))
}
}
}
}
ChartMuseum Multitenancy Support
ChartMuseum supports a multitenancy mode that lets you organize repositories into a hierarchy. The depth of the hierarchy is specified in the server configuration, with zero (single-tenant server) being the default.
To publish charts to a multitenancy-enabled ChartMuseum server, add one or more tenant identifiers to the tenantIds
list property in the repository configuration block. The number of tenant identifiers should match the depth configured
on the server.
helm {
publishing {
repositories {
chartMuseum {
url = uri('https://chartmuseum.example.com')
// For a multitenancy-enabled server with depth 2, use 2 tenant IDs
tenantIds.addAll('org1', 'repo2')
}
}
}
}
helm {
publishing {
repositories {
chartMuseum {
url.set(uri("https://chartmuseum.example.com"))
// For a multitenancy-enabled server with depth 2, use 2 tenant IDs
tenantIds.addAll("org1", "repo2")
}
}
}
}
Harbor Repositories
The plugin provides direct support for Harbor repositories. The project name can be set in the
repository configuration block, and defaults to library
if not set:
helm {
publishing {
repositories {
harbor {
url = uri('https://harbor.example.com')
projectName.set("my-project")
}
}
}
}
helm {
publishing {
repositories {
harbor {
url.set(uri("https://harbor.example.com"))
// For a multitenancy-enabled server with depth 2, use 2 tenant IDs
projectName.set("my-project")
}
}
}
}
Harbor uses ChartMuseum internally for its chart repositories, and it behaves like a multi-tenant
ChartMuseum server with two levels of depth (where the first-level tenant ID always seems to be chartrepo , and
the second-level tenant ID is the project name).
|
Nexus Repositories
The plugin provides direct support for Nexus repositories. The repository name can be set in the
repository configuration block, and defaults not using if not set.
Nexus API version can be specified by property apiVersion
default v1
if not set.
helm {
publishing {
repositories {
nexus {
url = uri('http://nexus.example.com')
repository = 'helm-repository'
apiVersion = 'v1'
}
}
}
}
helm {
publishing {
repositories {
nexus {
url.set(uri('http://nexus.example.com'))
repository.set("helm-repository")
apiVersion.set("v1")
}
}
}
}
Nexus API documentation. |
Gitlab Repositories
The plugin provides direct support for Gitlab repositories. The Gitlab API url
and the projectId
must be set in the
repository configuration block.
helm {
publishing {
repositories {
gitlab {
url = uri('https://gitlab.example.com/api/v4')
projectId = 1234
}
}
}
}
helm {
publishing {
repositories {
gitlab {
url.set(uri("https://gitlab.example.com/api/v4"))
projectName.set(1234)
}
}
}
}
Helm charts in the Gitlab Package Registry documentation. |
Custom Repositories
If your target repository is not directly supported but involves some sort of HTTP upload, you can try the custom
type which offers some (limited) possibilities to configure a freestyle upload.
Use the uploadMethod
, multipartForm
and/or uploadPath
properties to customize the upload request:
helm {
publishing {
repositories {
custom {
url = uri('http://helm-repo.example.com')
uploadMethod = 'PUT'
multipartForm = true
uploadPath = '/charts/{name}/{version}/{filename}'
}
}
}
}
helm {
publishing {
repositories {
custom {
url.set(uri("http://helm-repo.example.com"))
uploadMethod.set("PUT")
multipartForm.set(true)
uploadPath.set("/charts/{name}/{version}/{filename}")
}
}
}
}
The following placeholders can be used in the uploadPath
property:
-
{name}
will be replaced with the chart name -
{version}
will be replaced with the chart version -
{filename}
will be replaced with the file name of the packaged chart, i.e.{name}-{version}.tgz
Specifying Credentials for Repositories
Most likely, a chart repository will require some credentials for write access. You can configure
credentials in the same way as for repositories
:
helm {
publishing {
repositories {
example {
url = uri('http://helm-repo.example.com/')
credentials {
username = 'user'
password = 'password'
}
}
}
}
}
helm {
publishing {
repositories {
create("example") {
url.set(uri("http://helm-repo.example.com/"))
}
credentials {
username.set("user")
password.set("password")
}
}
}
}
Preventing a Chart from Being Published
By default, all charts defined in the project will be published. You can prevent this for a specific
chart by setting its publish
property to false
:
helm.charts {
// This chart will not be published
unpublishedChart {
// ...
publish = false
}
}
helm.charts {
// This chart will not be published
create("unpublishedChart") {
// ...
publish = false
}
}
Managing Releases
With the helm-releases
plugin, you can manage Helm releases on a remote Kubernetes cluster.
It allows you to define your releases in the Gradle DSL, in a declarative fashion. In this way, it can be used as an alternative to tools like Helmfile or Helmsman, with the advantage of leveraging the full power of Gradle instead of defining a custom file format.
Apply the helm-releases
plugin to your project:
plugins {
id 'com.citi.helm-releases' version '2.2.0'
}
plugins {
id("com.citi.helm-releases") version "2.2.0"
}
Define your releases using the helm.releases
block in your Gradle script:
helm {
releases {
mariadb {
from 'stable/mariadb'
version = '5.1.1'
// pass values (like --set on the command line)
values = [ 'rootUser.password': 'secret' ]
// pass value files (like -f on the command line)
valueFiles.from 'mariadb.yaml'
}
}
}
helm {
releases {
create("mariadb") {
from("stable/mariadb")
version.set("5.1.1")
// pass values (like --set on the command line)
values.set(mapOf("rootUser.password" to "secret"))
// pass value files (like -f on the command line)
valueFiles.from("mariadb.yaml")
}
}
}
There are quite a few properties you can set for a release; most of them correspond to a command line
option in helm install
, helm upgrade
or helm uninstall
.
The from
method is quite powerful, as it accepts various sources for the chart from which the release
will be created. Besides a String
(for specifying the chart directly), it can also be a File
,
RegularFile
, Directory
, URI
or also a Gradle Provider
of any of these types.
It is also possible to use a FileCollection
(e.g. a Gradle Configuration
), which should consist of
only one file. In that case, any
build dependencies
expressed by the FileCollection
will be honored by the release.
Of course you can also reference charts built by the helm
plugin, by just passing the chart’s DSL object
to from
:
helm {
charts {
foo {
// configure the foo chart ...
}
}
releases {
foo {
from charts.foo
}
}
}
helm {
val foo by charts.creating {
// configure the foo chart ...
}
releases {
create("foo") {
from(foo)
}
}
}
You can also refer to a chart by name (and optionally project) by using the chart
helper function:
// Chart in the same project, equivalent to charts.foo
from chart('foo')
// foo chart in the foo project
from chart(project: ':foo', chart: 'foo')
// main chart in the foo project
from chart(project: ':foo')
// Chart in the same project, equivalent to charts["foo"]
from(chart("foo"))
// foo chart in the foo project
from(chart(project = ":foo", chart = "foo"))
// main chart in the foo project
from(chart(project = ":foo"))
Release Tasks
For each release defined in the releases
block, the following Gradle tasks will be generated:
- Task
helmInstall<X>
-
Installs the release named X. This task will also do upgrades; depending on the
replace
property it will either callhelm upgrade --install
(by default) orhelm install --replace
. - Task
helmUninstall<X>
-
Uninstalls the release named X (by calling
helm uninstall
).
In addition, there will be the following tasks to manage all releases in the project at once:
- Task
helmInstall
-
Install or upgrade all releases.
- Task
helmUninstall
-
Uninstall all releases configured in the build script.
If you use a chart built by the helm plugin for a release, the corresponding helmInstall
task will have a task dependency on the helmPackage task so that the chart is guaranteed to be
up to date before it is installed.
|
Release Installation Order
You can influence the order in which releases are installed or uninstalled by calling mustInstallAfter
on the release. This is similar to what the mustRunAfter
method from the Gradle DSL does for tasks:
mustInstallAfter
does not express a "hard" dependency on another release; instead it only influences
the order in which their install tasks are called when both are requested to be installed in the current
Gradle invocation.
Currently it is not possible to refer to releases in another Gradle project.
While mustInstallAfter influences the order of helm install invocations, it does not guarantee
that the release will be up and running on the cluster when the installation of the dependent release
begins. By default, helm install does not wait until the deployment is complete — if this is what
you need, you can set wait to true in the release, so that the install/upgrade command is invoked
with the --wait flag.
|
helm.releases {
postgres {
from 'stable/postgresql'
// Set the wait property to true if the following release
// requires this to be successfully deployed
wait = true
// You can set the waitForJobs property in addition to wait,
// so we will also wait for all hook jobs to be completed
waitForJobs = true
}
myApp {
// ...
mustInstallAfter 'postgres'
}
}
helm.releases {
create("postgres") {
from("stable/postgresql")
// Set the wait property to true if the following release
// requires this to be successfully deployed
wait.set(true)
// You can set the waitForJobs property in addition to wait,
// so we will also wait for all hook jobs to be completed
waitForJobs.set(true)
}
create("myApp") {
// ...
mustInstallAfter("postgres")
}
}
Similarly, influencing the order of uninstalls is also possible using the mustUninstallAfter
method. Note that
mustInstallAfter
and mustUninstallAfter
are completely independent; neither of them implies the other. If you
want to express both installation and uninstallation ordering between two releases, you must do so explicitly by
calling both mustInstallAfter
and mustUninstallAfter
:
helm.releases {
postgres {
from 'stable/postgresql'
// Set the wait property to true if the following release
// requires this to be successfully deployed
wait = true
// We can refer to a release by name even before it is declared --
// the references are resolved lazily
mustUninstallAfter 'myApp'
}
myApp {
// ...
mustInstallAfter 'postgres'
}
}
helm.releases {
create("postgres") {
from("stable/postgresql")
// Set the wait property to true if the following release
// requires this to be successfully deployed
wait = true
// We can refer to a release by name even before it is declared --
// the references are resolved lazily
mustUninstallAfter("myApp")
}
create("myApp") {
// ...
mustInstallAfter("postgres")
}
}
Release Dependencies
This feature is now deprecated as of version 1.2.0, and will removed in a future version. Instead, please use tags to specify which releases to install, and Release Installation Order to indicate in which order they should be installed. Release dependencies are now deprecated because the side effects of automatically installing or uninstalling a dependent release may often be undesirable. If the dependent release is already installed, then it would always be upgraded even if that is not necessary. Likewise, the automatic uninstallation is not always desired and should be controlled in a more fine-grained way using the other mechanisms documented here. |
The dependsOn
property and method on a release allows you to indicate that a certain release depends on another
release. As a consequence, when release A depends on release B, then release B will automatically installed before
release A is installed, and uninstalled after release A is uninstalled.
helm.releases {
postgres {
from 'stable/postgresql'
}
myApp {
// myApp depending on postgres means that whenever myApp is installed,
// postgres will be automatically installed first; and whenever myApp
// is uninstalled, postgres will be uninstalled afterwards.
dependsOn 'postgres'
}
}
helm.releases {
create("postgres") {
from("stable/postgresql")
}
create("myApp") {
// myApp depending on postgres means that whenever myApp is installed,
// postgres will be automatically installed first; and whenever myApp
// is uninstalled, postgres will be uninstalled afterwards.
dependsOn("postgres")
}
}
Install/Upgrade logic
The installation task that the plugin creates for each release, named helmInstall<Name>Release
, will perform an
install or upgrade based on the following logic:
-
if the release has the
replace
property set totrue
, it will always callhelm install --replace
-
Otherwise, it calls
helm ls
as an intermediate step to determine the current status of the release.-
if the release exists but previously failed, it will call
helm install --replace
. This works around theUPGRADE FAILED: "<name>" has no deployed releases
issue if a previous call tohelm install
was not successful. -
Otherwise, it will call
helm upgrade --install
(also if the release does not exist).
-
Installation Task Dependencies
Sometimes it is necessary to execute other task dependencies before a release can be installed. For example, when
using Helm and jib together, you may want to
call the jib
task that builds your Docker image before you install the release.
You can declare such dependencies using the installDependsOn
property or method on the release:
helm.releases {
myApp {
// ...
installDependsOn 'jib'
}
}
helm.releases {
create("myApp") {
// ...
installDependsOn("jib")
}
}
Using Release Targets
A frequent requirement when using Helm is to designate different release targets, with target-specific configuration applied to each chart depending on the target. For example, you might use release targets to model different server environments, stages of development (e.g. dev / test / production), or different variants of installation.
Defining Release Targets
Use the helm.releaseTargets
container in the Gradle build script to add release targets. For example, you might
want to use a different kubeContext
for each target, and use the --atomic
flag only for production installs:
helm {
releaseTargets {
local {
kubeContext = 'docker-for-desktop'
}
production {
kubeContext = 'aws'
atomic = true
}
}
}
helm {
releaseTargets {
create("local") {
kubeContext.set("docker-for-desktop")
}
create("production") {
kubeContext.set("aws")
atomic.set(true)
}
}
}
INFO: If you don’t create your own release targets, the plugin will create a release target named default
that
uses all the default properties. As soon as you create other release targets, the default
target will back away.
Values (and related properties) can be added to a release target as well, and they will be used for every release that is installed to this target:
helm {
releaseTargets {
local {
values.put('replication.enabled', false)
}
}
}
helm {
releaseTargets {
create("local") {
values.put("replication.enabled", false)
}
}
}
When a release is installed to a target, the parameters for the helm install
/ helm upgrade
call are determined
from the properties of both the release and the release target. If a property is defined on both the release and the
release target, the release has precedence (except for values, which will be merged from both sources).
Installing to and Uninstalling From a Specific Target
For each Gradle run, one of the release targets will be the active release target. This can be controlled by setting
the helm.release.target
property:
./gradlew helmInstall -Phelm.release.target=production
It is also useful to set a default value for this property in your gradle.properties
file, to indicate a default
release target that can be selectively overridden on the command line:
helm.release.target=local
For each Gradle build with the plugin, there can only ever be one active release target. You cannot install
charts to multiple targets from within the same build. Even though the plugin registers tasks like
helmInstallMyReleaseToLocal even for inactive targets, those will be SKIPPED when part of the task execution graph.
|
Target-Specific Release Configuration
Inside a release
, you can add a forTarget { }
block that applies configuration only for a specific target.
For example, many Helm charts can be configured to create a Secret
or use an existing secret:
helm {
releaseTargets {
local
production
}
releases {
myApp {
from 'my-repo/my-application'
forTarget('local') {
values.put('auth.username', 'username')
values.put('auth.password', 'password')
}
forTarget('production') {
values.put('auth.existingSecret', 'some-existing-secret')
}
}
}
}
helm {
releaseTargets {
create("local")
create("production")
}
releases {
create("myApp") {
from("my-repo/my-application")
forTarget("local") {
values.put("auth.username", "username")
values.put("auth.password", "password")
}
forTarget("production") {
values.put("auth.existingSecret", "some-existing-secret")
}
}
}
}
If you prefix the argument to forTarget with an exclamation mark (e.g. forTarget("!local") ), the block
will be evaluated for all targets but the given one.
|
forTarget
blocks are evaluated lazily when the release is actually installed to the given target. You can modify
most of the properties of the release
in the forTarget
block. For example, to add additional installation task
dependencies based on the target, call installDependsOn
inside a forTarget
block:
helm {
releases {
myApp {
from 'my-repo/my-application'
forTarget('local') {
installDependsOn 'jibDockerBuild'
}
forTarget('production') {
installDependsOn 'jib'
}
}
}
}
helm {
releases {
create("myApp") {
from("my-repo/my-application")
forTarget("local") {
installDependsOn("jibDockerBuild")
}
forTarget("!local") {
installDependsOn("jib")
}
}
}
}
If you need to apply dynamic target-specific configuration that depends on the target name, the scope of each
forTarget
block contains a target
property that allows access to the target for which the release is being
configured. In the case where this target
reference is all you need, you could even use forAnyTarget
, which
is always called.
For example, to pass a specific file to fileValues
, whose name depends on the release target name,
you could do this:
helm {
releases {
myApp {
from 'my-repo/my-application'
forAnyTarget {
fileValues.put('tls.certificate', "cert/cert-${target.name}.pem")
}
}
}
}
helm {
releases {
create("myApp") {
from("my-repo/my-application")
forAnyTarget {
fileValues.put("tls.certificate", "cert/cert-${target.name}.pem")
}
}
}
}
For more advanced scenarios, both the release and the release target DSL objects are ExtensionAware , which
means they have an extra properties extension where you can store custom properties that can then be accessed
in a forTarget or forAnyTarget block.
|
Directories of Value Files
Since values and value files are the most common thing to customize per target, there exists another handy mechanism
which allows you to declare a valuesDir
directory containing YAML files with filenames according to a specific
convention. Assuming you have a directory containing value files like this:
📂 myapp-values 📄 values.yaml 📄 values-local.yaml 📄 values-production.yaml
Pass the path of the directory to the release by calling valuesDir
:
helm {
releases {
myApp {
from 'my-repo/my-application'
valuesDir 'myapp-values'
}
}
}
helm {
releases {
create("myApp") {
from("my-repo/my-application")
valuesDir("myapp-values")
}
}
}
When installing a release to a given target, the values-<target>.yaml
file is automatically passed to the
helm install
or helm upgrade
command. If the directory contains a values.yaml
file (without a suffix), then it
will be used for all targets, but with a lower precedence than the target-specific values files, meaning you can
define common defaults in values.yaml
and then selectively override them for each target.
Each of the files in the values directory is optional, if a certain file name pattern does not exist, it will not be used.
In the above example, when installing myApp
to the local
target, it would call helm install
/ helm upgrade
with the option --values myapp-values/values.yaml,myapp-values/values-local.yaml
. Similarly, when installing to the
production
target, it would use the option --values myapp-values/values.yaml,myapp-values/values-production.yaml
.
Use the built-in Gradle mechanisms to automatically declare a Groovy
Kotlin
|
Using Tags to Select Releases
Often you want to declare multiple releases in your build script, but you want to selectively install only a subset of them. For example, when installing a set of applications in a local cluster, you might want to include infrastructure components like a database or message queue as well, but exclude them when installing to production because they can be assumed to be managed externally.
The helm-releases
plugin offers a powerful tagging mechanism for such setups:
-
Each release may be assigned a set of tags
-
A release target may contain a selection expression to use only a subset of releases
-
A global selection expression allows you to further filter the set of releases per-build
In the following example, we create two releases myApp
and mongodb
, and assign them tags application
and
database
, respectively:
helm.releases {
myApp {
from 'my-repo/my-application'
tags 'application'
}
mongodb {
from 'bitnami/mongodb'
tags 'database'
}
}
helm.releases {
create("myApp") {
from("my-repo/my-application")
tags("application")
}
create("mongodb") {
from("bitnami/mongodb")
tags("database")
}
}
Now we can specify a select expression in our release targets to configure which releases should be installed to
each target. In its simplest form, the select expression is just the name of a tag, which must then be present on
the release. The expression *
(which is also the default) matches any release.
helm.releaseTargets {
local {
// this is the default
selectTags = '*'
}
production {
// When installing to production, only install releases tagged "application"
selectTags = 'application'
}
}
helm.releaseTargets {
create("local") {
// this is the default
selectTags = "*"
}
create("production") {
// When installing to production, only install releases tagged "application"
selectTags = "application"
}
}
You can combine tags in a select expression using the operators &
(and), |
(or) and !
(not). Comma and space also
mean "or". Some examples:
-
!database
matches any release except ones tagged withdatabase
-
application,infrastructure
matches any release taggedapplication
orinfrastructure
-
infrastructure & !database
matches any release taggedinfrastructure
, but not taggeddatabase
In addition to setting selectTags
on a release target, you can also set the property helm.release.tags
on the
command line to further narrow down the set of releases per-build.
# this will install only "application"-tagged releases to the "local" target
./gradlew helmInstall -Phelm.release.target=local -Phelm.release.tags=application
INFO: If both helm.release.tags
and the active release target contain a select expression for tags, they will be
both combined using "and". This means that a release will only be installed if it matches both the helm.release.tags
expression and the selectTags
expression of the release target.
Testing Releases
Helm includes a facility for testing charts after they have been installed on a remote cluster. Tests are Kubernetes
Job
resources with an annotation helm.sh/hook: test
. See the
Chart Tests section in the Helm documentation for details.
The helm-releases
plugin integrates chart testing by exposing a HelmTest
task for every release that is declared
in the build script. Like other release options, tests can be configured (or even completely disabled) for a release,
a release target, or a specific combination of the two.
Release Testing Tasks
For each release defined in the releases
block, the following Gradle task will be generated:
- Task
helmTest<X>
-
Tests the release named X on the active release target (as indicated by
-Phelm.release.target
) by callinghelm test <X>
.
In addition, there will be the following tasks to test all releases in the project at once:
- Task
helmTest
-
Test all releases (except those that have the
enabled
property set tofalse
).
If your chart does not contain any tests, helm test (and therefore the HelmTest task) will simply do nothing.
|
A helmTest<X>
task created for a release will not automatically have a task dependency on the corresponding
helmInstall<X>
task. (It does have a mustRunAfter
relationship, however.) This means that simply calling
gradle helmTest<X>
will not automatically install the release. This allows you to use the helmTest
or helmTest<X>
tasks even for testing releases that are already deployed.
If you want to install and test releases in the same Gradle build, then you need to include both helmInstall
and
helmTest
in the Gradle invocation, e.g.
gradle helmInstall helmTest
In this case, you should also set the wait
property to true
on the release, to ensure that the release
is up and running before the test starts.
Release Testing Configuration
You can fine-tune testing options using the test
DSL block inside a release or release target.
helm {
releases {
foo {
from 'my-repo/foo'
test {
// dump test logs
showLogs = true
}
}
bar {
from 'my-repo/bar'
test {
// disable testing for this release (enabled by default)
enabled = false
}
}
}
releaseTargets {
local {
test {
// always dump test logs for this target
showLogs = true
}
}
remote {
test {
// configure a different timeout for this target
timeout = Duration.ofSeconds(10)
}
}
}
}
helm {
releases {
create("foo") {
from("my-repo/foo")
test {
// dump test logs
showLogs.set(true)
}
}
create("bar") {
from("my-repo/bar")
test {
// disable testing for this release (enabled by default)
enabled.set(false)
}
}
}
releaseTargets {
create("local") {
test {
// always dump test logs for this target
showLogs.set(true)
}
}
create("remote") {
test {
// configure a different timeout for this target
timeout.set(Duration.ofSeconds(10))
}
}
}
}
The showLogs
property (which corresponds to the --logs
flag to the helm test
CLI command) can also be configured
globally on a per-invocation basis by setting the helm.test.logs
property:
gradle helmTest -Phelm.test.logs=true
Checking the Status of a Release
For each release/target combination, the helm-releases
plugin will provide a task named
helmStatus<Release>On<Target>
to check the status of the release. Internally, this will map to a call of
helm status <release-name>
.
For example, to check the status of the "awesome" release on the "local" target:
gradle helmStatusAwesomeOnLocal
In addition, you can also check the status of a release on the active target (as indicated by the helm.release.target
project property) using the helmStatus<Release>
task:
gradle helmStatusAwesome -Phelm.release.target=local
Controlling the output file and format
helm status
offers an --output
option that allows you to select the desired output format for the status report.
With the helm-releases
plugin, this can be achieved by setting the helm.status.outputFormat
property:
gradle helmStatusAwesome -Phelm.status.outputFormat=yaml
You can also output the status report to a file instead of stdout, by setting the helm.status.outputFile
property:
gradle helmStatusAwesome -Phelm.status.outputFile='helm-status.json'
Relative paths are resolved from the project directory of the HelmStatus
task, which might not be the root directory.
You can use Groovy GString expansion in the property value to force it to the root directory (but remember to
properly quote “$” characters when calling from a shell):
gradle helmStatusAwesome -Phelm.status.outputFile='$rootDir/build/helm-status.json'
If an output file is used, and the output format is not explicitly set, the correct format is automatically
guessed based on the file extension. For example, for an output file with the .json extension, the output format
defaults to json (instead of table , which is Helm’s default).
|
Using Helm Commands
The plugin com.citi.helm-commands
allows you to use Helm commands directly, similar to what you would do
with the Helm CLI. helm-commands
is also implied by all the other plugins in the suite.
For most Helm CLI commands, there is a corresponding Gradle task type (except for commands that do not really make
sense for a build script, like helm search
or helm inspect
).
In most cases it is more convenient to use the high-level DSL provided by the other plugins to manage your charts in a declarative way. |
Check the API Reference for a full list of available tasks.
Configuration from Project Properties
Many settings can be configured by Gradle
project properties
instead of specifying them in the DSL. This has the advantage that you can pass them on the command line
(using -P
switches) or a local ~/gradle.properties
file to account for different build environments.
In addition, such properties are automatically inherited by subprojects.
Some of these properties allow evaluation as a Groovy GString
, so that you can do things like
-Phelm.executable=$rootDir/helm/bin/helm3
(but the dollar signs may need to be escaped so the shell does not
treat them as environment variables). GStrings will be evaluated in the context of each project.
In general, the name of the project property corresponds to the path of the property in the DSL,
e.g. helm.executable
.
Properties set explicitly in your Gradle script have precedence over properties from the command line
or gradle.properties file, so it may be better to put them in gradle.properties in the first place, to
allow for easier overriding.
|
Basic Helm Properties
Property | Description | GString |
---|---|---|
|
Path to the Helm CLI executable. The |
|
|
Enable verbose output from the Helm CLI. |
|
|
Name of the kubeconfig context to use. |
|
|
Path to the Kubernetes configuration file. |
|
|
Time to wait for an individual Kubernetes operation (like Jobs or hooks). May be specified in Helm format (e.g. |
|
|
The target namespace for Kubernetes operations. |
|
|
The base output directory for charts; defaults to |
|
|
Base temporary directory for various intermediate artifacts. Defaults to |
|
|
Base directory for storing data. Corresponds to the |
|
|
Base directory for storing configuration. Corresponds to the |
|
|
Base directory for storing data. Corresponds to the |
Repositories
You can configure repositories entirely from Gradle properties — just the presence of a set of
helm.repositories.<name>.<xyz>
properties will automatically create a corresponding repository.
Property | Description |
---|---|
|
The URL of the repository. |
|
Username for password-based authentication. |
|
Password for password-based authentication. |
|
Client certificate file for certificate authentication. |
|
Private key file for certificate authentication. |
Filtering
Property | Description |
---|---|
|
Globally enable or disable filtering. Defaults to |
Linting
Property | Description |
---|---|
|
Globally enable or disable linting. Defaults to |
|
Globally enable strict mode for linting. Defaults to |
Rendering
Property | Description |
---|---|
|
The base directory under which the outputs of renderings will be placed. The output for a particular rendering will be placed in a
subdirectory |
Releases
Property | Description |
---|---|
|
Perform releases atomically (use |
|
Only perform a dry run when installing or deleting releases. |
|
Use the |
|
Specify the active release target. Defaults to |
|
Specify an expression to filter releases by tag. By default, all releases are matched. |
|
Use the |
|
Use the |