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 between v1 and v2 is that v1 declares chart dependencies in a file called requirements.yaml, whereas in v2 the dependencies are declared directly in the Chart.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:

Groovy
plugins {
    id 'com.citi.helm' version '2.2.0'
}
Kotlin
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.

Groovy
plugins {
    id 'com.citi.helm-commands' version '2.2.0'
}
Kotlin
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.

Groovy
plugins {
    id 'com.citi.helm' version '2.2.0'
}
Kotlin
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.

Groovy
plugins {
    id 'com.citi.helm-publish' version '2.2.0'
}
Kotlin
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.

Groovy
plugins {
    id 'com.citi.helm-releases' version '2.2.0'
}
Kotlin
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 gradle.properties or on the command line instead. See Configuration from Project Properties.

Global Helm Options

The following properties can be set on the helm extension in the Gradle build script.

Property Description helm CLI option

executable

Path to the helm executable. The PATH environment variable is taken into account, so this can just be helm (which is the default) if the Helm client is installed in a suitable location.

extraArgs

Additional arguments to pass to every invocation of helm. This can be used for command-line options that have no counterpart in the plugin.

kubeConfig

Path to the Kubernetes configuration file.

Environment variable KUBECONFIG

kubeContext

Name of the kubeconfig context to use.

--kube-context

namespace

Kubernetes namespace scope.

--namespace

remoteTimeout

Time to wait for any individual Kubernetes operation (like Jobs for hooks).

--timeout (on certain commands like install, upgrade or uninstall)

The environment variable KUBECONFIG is also honored by the plugin, but it has lower precedence than setting kubeConfig in the build script or with the helm.kubeConfig project property.

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

xdgDataHome

Base directory for storing data. (Note: this does not currently seem to be used by any helm command.)

XDG_DATA_HOME

build/helm/data under the project dir

xdgConfigHome

Base directory for storing configuration.

XDG_CONFIG_HOME

build/helm/config under the project dir

xdgCacheHome

Base directory for storing cached data.

XDG_CACHE_HOME

.gradle/helm/cache under the root project dir

The cache home, as indicated by xdgCacheHome, defaults to a directory under the root project, meaning that in a multi-module build, all Helm projects will share the same cache by default. This speeds up builds, but may lead to problems in certain situations, for example if two projects register a different chart repository under the same name.

+ If necessary, you can always set the cache home to a project-local directory, for example:

Groovy
helm {
    xdgCacheHome = file("${project.buildDir}/helm/cache")
}
Kotlin
helm {
    xdgCacheHome.set(file("${project.buildDir}/helm/cache"))
}

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:

Groovy
helm {
    downloadClient {
        enabled = true
        version = '3.4.1'
    }
}
Kotlin
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:

gradle.properties
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

enabled

helm.client.download.enabled

If set to true, the Helm client is downloaded from the remote location.

false

version

helm.client.download.version

The version of the Helm client to be downloaded.

The latest version of Helm available at the time the plugin is published (currently 3.4.1)

helm.client.download.baseUrl

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.

https://get.helm.sh/

helm.client.download.osClassifier

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, darwin-amd64 or windows-amd64.

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

chartName

The name of the chart, as used in various helm CLI commands. The plugin will make sure that the chart sources are in a directory with this name when calling helm.

Defaults to the name of the chart in the DSL.

chartVersion

The version of the chart, as used by helm.

Defaults to the Gradle project version.

sourceDir

The directory containing the chart sources (Chart.yaml file, etc.).

Defaults to src/main/helm for the main chart (see below), required for all other charts.

extraFiles

An optional Gradle CopySpec that allows injecting files from external sources into the chart.

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:

Groovy
helm.charts {
    main {
        sourceDir = file('src/main/helm')
    }
}
Kotlin
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:

Groovy
helm.charts.main.chartVersion = '1.0.0'
Kotlin
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 name declared in the Chart.yaml file differs from the name of the containing directory. If you want to avoid such a warning, you can either * call your chart helm in the Chart.yaml file — it will automatically be renamed when the chart is packaged; or * change the sourceDir of the main chart to a directory that matches the chart name.

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:

Groovy
subprojects {
    // Use verbose logging on all Helm commands
    helm.extraArgs.addAll("-v", "1")
}
Kotlin
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.

Groovy
helm.charts {
    myChart {
        sourceDir = file 'src/helm/my-chart'

        extraFiles {
            from('src/extra/script.js') {
                into('files/scripts')
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    repositories {
        example {
            url 'http://helm-repo.example.com/'
        }
    }
}
Kotlin
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:

Groovy
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()
    }
}
Kotlin
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.

Groovy
helm {
    repositories {
        example {
            url 'http://helm-repo.example.com/'
            credentials {
                username = 'user'
                password = 'password'
            }
        }
    }
}
Kotlin
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:

Groovy
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'
            }
        }
    }
}
Kotlin
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

chartName

The chart name, as set by the HelmChart.chartName property in the build script

chartVersion

The chart version, as set by the HelmChart.chartVersion property in the build script

projectVersion

The version of the Gradle Project (at the time the filtering task is executed)

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:

Groovy
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)
        }
    }
}
Kotlin
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.

Groovy
helm {
    filtering {
        // apply filtering to all files in the chart
        filePatterns.add('*')
    }
}
Kotlin
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.

Groovy
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'
            }
        }
    }
}
Kotlin
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:

values.yaml
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:

build.gradle
helm {
    filtering {
        values.put 'imageRepository', jib.to.image
        values.put 'imageTag', jib.to.tags.first()
    }
}
build.gradle.kts
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:

values.yaml
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:

Groovy
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
}
Kotlin
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):

values.yaml
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.

Groovy
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
            }
        }
    }
}
Kotlin
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.

Groovy
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'
            }
        }
    }
}
Kotlin
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).

Groovy
helm {

    lint {
        // disable linting by default
        enabled = false
    }

    charts {
        main {
            // enable linting for the main chart
            lint.enabled = true
        }
    }
}
Kotlin
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.

Groovy
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'
                    }
                }
            }
        }
    }
}
Kotlin
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:

Groovy
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'
    }
}
Kotlin
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:

Groovy
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
    }
}
Kotlin
(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:

bar/Chart.yaml
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:

Groovy
dependencies {
    // Assuming that the foo chart is defined in the foo project
    add project: ':foo', chart: 'foo'
}
Kotlin
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:

Groovy
dependencies {
    // Referencing the main chart in the foo project
    add project: ':foo'
}
Kotlin
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.

Groovy
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'
}
Kotlin
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:

Groovy
helm.charts {
    awesome {
        // ... configure the chart
    }
}
Kotlin
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:

Groovy
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')
            }
        }
    }
}
Kotlin
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:

Groovy
helm.charts {
    main {
        renderings {
            withExistingSecret {
                values.put('existingSecret', 'secretName')
            }
        }
    }
}
Kotlin
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 the helmRenderMainChartWithExistingSecretRendering task to execute the withExistingSecret rendering.

  • Task helmRenderMainChart will execute all renderings of the main chart (including the default 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 custom repository type, or consider creating a PR to add support for it.

Apply the helm-publish plugin to your project:

Groovy
plugins {
    id 'com.citi.helm-publish' version '2.2.0'
}
Kotlin
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:

Groovy
helm {
    publishing {
        repositories {
            chartMuseum('example') {
                url = uri('http://helm-repo.example.com/')
            }
            artifactory('myRepo') {
                url = uri('http://artifactory.example.com/helm-local')
            }
        }
    }
}
Kotlin
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 helm.repositories and the publishing repositories in helm.publishing.repositories. The former are for retrieving charts, the latter are for publishing them.

If you want to download from and publish to the same external repository, you would need to specify it both in helm.repositories and helm.publishing.repositories (similar to Gradle’s built-in publishing).

If you only define a single publishing repository, the name can be omitted, in which case the name "default" is used:

Groovy
helm {
    publishing {
        repositories {
            artifactory {
                url = uri('http://artifactory.example.com/helm-local')
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    publishing {
        repositories {
            chartMuseum {
                url = uri('https://chartmuseum.example.com')
            }
        }
    }
}
Kotlin
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.

Groovy
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')
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    publishing {
        repositories {
            harbor {
                url = uri('https://harbor.example.com')
                projectName.set("my-project")
            }
        }
    }
}
Kotlin
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.

Groovy
helm {
    publishing {
        repositories {
            nexus {
                url = uri('http://nexus.example.com')
                repository = 'helm-repository'
                apiVersion = 'v1'
            }
        }
    }
}
Kotlin
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.

Groovy
helm {
    publishing {
        repositories {
            gitlab {
                url = uri('https://gitlab.example.com/api/v4')
                projectId = 1234
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    publishing {
        repositories {
            custom {
                url = uri('http://helm-repo.example.com')
                uploadMethod = 'PUT'
                multipartForm = true
                uploadPath = '/charts/{name}/{version}/{filename}'
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    publishing {
        repositories {
            example {
                url = uri('http://helm-repo.example.com/')
                credentials {
                    username = 'user'
                    password = 'password'
                }
            }
        }
    }
}
Kotlin
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:

Groovy
helm.charts {

    // This chart will not be published
    unpublishedChart {
        // ...
        publish = false
    }
}
Kotlin
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:

Groovy
plugins {
    id 'com.citi.helm-releases' version '2.2.0'
}
Kotlin
plugins {
    id("com.citi.helm-releases") version "2.2.0"
}

Define your releases using the helm.releases block in your Gradle script:

Groovy
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'
        }
    }
}
Kotlin
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:

Groovy
helm {
    charts {
        foo {
            // configure the foo chart ...
        }
    }

    releases {
        foo {
            from charts.foo
        }
    }
}
Kotlin
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:

Groovy
// 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')
Kotlin
// 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 call helm upgrade --install (by default) or helm 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.
Groovy
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'
    }
}
Kotlin
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:

Groovy
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'
    }
}
Kotlin
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.

Groovy
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'
    }
}
Kotlin
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 to true, it will always call helm 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 the UPGRADE FAILED: "<name>" has no deployed releases issue if a previous call to helm 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:

Groovy
helm.releases {

    myApp {
        // ...
        installDependsOn 'jib'
    }
}
Kotlin
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:

Groovy
helm {
    releaseTargets {
        local {
            kubeContext = 'docker-for-desktop'
        }
        production {
            kubeContext = 'aws'
            atomic = true
        }
    }
}
Kotlin
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:

Groovy
helm {
    releaseTargets {
        local {
            values.put('replication.enabled', false)
        }
    }
}
Kotlin
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:

gradle.properties
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:

Groovy
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')
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    releases {
        myApp {
            from 'my-repo/my-application'
            forTarget('local') {
                installDependsOn 'jibDockerBuild'
            }
            forTarget('production') {
                installDependsOn 'jib'
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    releases {
        myApp {
            from 'my-repo/my-application'
            forAnyTarget {
                fileValues.put('tls.certificate', "cert/cert-${target.name}.pem")
            }
        }
    }
}
Kotlin
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:

Groovy
helm {
    releases {
        myApp {
            from 'my-repo/my-application'
            valuesDir 'myapp-values'
        }
    }
}
Kotlin
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 valuesDir for each release according to your own convention. Assuming your directory structure contains value files for each release in a subdirectory $projectDir/helm-values/<release-name>:

Groovy
helm.releases.all {
    valuesDir "helm-values/$name"
}
Kotlin
helm.releases.all {
    valuesDir("helm-values/$name")
}

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:

Groovy
helm.releases {
    myApp {
        from 'my-repo/my-application'
        tags 'application'
    }
    mongodb {
        from 'bitnami/mongodb'
        tags 'database'
    }
}
Kotlin
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.

Groovy
helm.releaseTargets {
    local {
        // this is the default
        selectTags = '*'
    }
    production {
        // When installing to production, only install releases tagged "application"
        selectTags = 'application'
    }
}
Kotlin
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 with database

  • application,infrastructure matches any release tagged application or infrastructure

  • infrastructure & !database matches any release tagged infrastructure, but not tagged database

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 calling helm 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 to false).

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.

Groovy
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)
            }
        }
    }
}
Kotlin
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.

Example

build.gradle
import com.citi.gradle.plugins.helm.command.tasks.*

plugins {
    id 'com.citi.helm-commands' version '<VERSION>'
}

task installMariaDb(type: HelmInstall) {
    chart = 'stable/mariadb'
    version = '4.3.1'
    releaseName = 'mariadb'
}

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

helm.executable

Path to the Helm CLI executable. The PATH variable is taken into account, so this can just be "helm" if the Helm client is installed in a suitable location. Defaults to helm.

helm.debug

Enable verbose output from the Helm CLI.

helm.kubeContext

Name of the kubeconfig context to use.

helm.kubeConfig

Path to the Kubernetes configuration file.

helm.remoteTimeout

Time to wait for an individual Kubernetes operation (like Jobs or hooks).

May be specified in Helm format (e.g. 3m30s), in ISO format (e.g. PT3M30S) or as a plain number indicating the number of seconds (e.g. 210).

helm.namespace

The target namespace for Kubernetes operations.

helm.outputDir

The base output directory for charts; defaults to $buildDir/helm/charts.

helm.tmpDir

Base temporary directory for various intermediate artifacts. Defaults to $buildDir/tmp/helm.

helm.xdgDataHome

Base directory for storing data. Corresponds to the XDG_DATA_HOME environment variable. Defaults to $buildDir/helm/data.

helm.xdgConfigHome

Base directory for storing configuration. Corresponds to the XDG_CONFIG_HOME environment variable. Defaults to $buildDir/helm/config.

helm.xdgCacheHome

Base directory for storing data. Corresponds to the XDG_CACHE_HOME environment variable. Defaults to $rootDir/.gradle/helm/cache.

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

helm.repository.<name>.url

The URL of the repository.

helm.repository.<name>.credentials.username

Username for password-based authentication.

helm.repository.<name>.credentials.password

Password for password-based authentication.

helm.repository.<name>.credentials.certificateFile

Client certificate file for certificate authentication.

helm.repository.<name>.credentials.keyFile

Private key file for certificate authentication.

Filtering

Property Description

helm.filtering.enabled

Globally enable or disable filtering. Defaults to true.

Linting

Property Description

helm.lint.enabled

Globally enable or disable linting. Defaults to true.

helm.lint.strict

Globally enable strict mode for linting. Defaults to false.

Rendering

Property Description

helm.renderOutputDir

The base directory under which the outputs of renderings will be placed.

The output for a particular rendering will be placed in a subdirectory <chart-name>/<rendering-name>.

Releases

Property Description

helm.atomic

Perform releases atomically (use --atomic flag).

helm.dryRun

Only perform a dry run when installing or deleting releases.

helm.noHooks

Use the --no-hooks flag when installing or uninstalling.

helm.release.target

Specify the active release target. Defaults to default.

helm.release.tags

Specify an expression to filter releases by tag. By default, all releases are matched.

helm.wait

Use the --wait flag when installing or upgrading.

helm.waitForJobs

Use the --wait-for-jobs flag when installing or upgrading.