1. Introduction
Helm is a Kubernetes package manager that deals with charts as its units of deployment. Like any other package manager, Helm resolves dependencies between its packages, i.e., charts. Further, it supports manually listing dependencies in certain conditions. However, it doesn’t provide a feature that shows a dependency tree for a given installed chart, i.e., release.
In this tutorial, we explore Helm dependency enumeration and resolution. First, we briefly refresh our knowledge about dependencies. After that, we see a native way to list dependencies with Helm. Next, we use shell scripting to leverage this native method for an installed chart also called a release. Finally, we demonstrate a more complex script that can extract charts from a release and list dependencies recursively.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15 and Helm 3.14. Unless otherwise specified, it should work in most POSIX-compliant environments.
2. Chart Dependencies
Even very basic Kubernetes deployments have many moving parts.
For instance, an empty Kubernetes cluster still runs multiple pods with different functions:
$ kubectl get all --namespace=kube-system
NAME READY STATUS RESTARTS AGE
pod/coredns-5dd0666b68-lp7b5 1/1 Running 1 9d
pod/etcd-xost 1/1 Running 1 9d
pod/kube-apiserver-xost 1/1 Running 1 9d
pod/kube-controller-manager-xost 1/1 Running 1 9d
pod/kube-proxy-tldfh 1/1 Running 1 9d
pod/kube-scheduler-xost 1/1 Running 1 9d
pod/storage-provisioner 1/1 Running 1 (6d22h ago) 9d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 9d
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 9d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coredns 1/1 1 1 9d
NAME DESIRED CURRENT READY AGE
replicaset.apps/coredns-5dd0666b68 1 1 1 9d
Further, Helm chart deployments usually result in releases with many pods and resources:
$ helm install gabe565/nightscout --generate-name --create-namespace --namespace nightscout
[...]
$ kubectl get all --namespace nightscout
NAME READY STATUS RESTARTS AGE
pod/nightscout-1709766626-f7d430108-tzp8j 1/1 Running 0 49s
pod/nightscout-1709766626-mongodb-0 1/1 Running 0 49s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nightscout-1709766626 ClusterIP 10.105.224.56 <none> 1337/TCP 49s
service/nightscout-1709766626-mongodb ClusterIP 10.101.52.236 <none> 27017/TCP 49s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nightscout-1709766626 1/1 1 1 49s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nightscout-1709766626-f7d430108 1 1 1 49s
NAME READY AGE
statefulset.apps/nightscout-1709766626-mongodb 1/1 49s
Because of these natural object trees, Helm officially supports dependencies with a formal syntax. For instance, a dependency chart can be a direct part of the dependent chart metadata via a name, version, and URL.
Notably, the exact syntax has changed through Helm versions:
- Helm 2 uses a separate requirements.yaml file
- Helm 3 embeds information in the Chart.yaml file
Still, dependency resolution works the same for both.
3. List Chart Dependencies
To begin with, let’s understand how to use the Helm dependency subcommand.
Unlike other package managers, Helm doesn’t provide dependency trees for installed or online packages.
Instead, we first have to download a chart and unpack it:
$ helm pull gabe565/ascii-movie --untar
In fact, pull supports –version selection similar to other Helm subcommands:
$ helm pull gabe565/ascii-movie --version 0.0.3 --untar
After [pull]ing the chart and unpacking it, we enter the resulting directory and list the files within.
$ cd ascii-movie
$ ls
Chart.lock charts Chart.yaml README.md templates values.yaml
Since we can see the Chart.yaml file along with the charts subdirectory, we can run the dependency subcommand of Helm:
$ helm dependency list
NAME VERSION REPOSITORY STATUS
common 1.5.1 https://bjw-s.github.io/helm-charts unpacked
Evidently, the ascii-movie chart has one dependency named common from the given REPOSITORY.
Let’s turn this process into a single command and generalize it:
$ helm pull <CHART> --untar && helm dependency list ./<CHART_NAME> && rm -rf <CHART_NAME>
Here, the syntax of CHART can be any of the options supported by the install subcommand. On the other hand, CHART_NAME is the name of the chart and usually the resulting directory and file names.
4. Installed Chart Dependencies
Although it’s not an official feature, we can come up with a way to get the dependencies of an installed chart via its release.
First, we get information about the release we want:
$ helm list nightscout-1709766626
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
nightscout-1709766626 nightscout 1 2024-03-08 03:08:24.619666646 -0500 EST deployed nightscout-0.10.0 15.0.2
In this case, the CHART that generated the release is nightscout-0.10.0.
So, *let’s find the chart information by [list]ing and [–filter]ing the releases from –all-namespaces*:
$ helm list --filter nightscout --all-namespaces
NAME CHART VERSION APP VERSION DESCRIPTION
gabe565/nightscout 0.10.0 15.0.2 Web-based CGM (Continuous Glucose Monitor) to a...
Notably, there can be complications during this step:
- the chart might be from the Artifact Hub
- multiple charts can have the same name in different repositories
- the repository might not be available
In these cases, we might have to switch to hub searching or perform some research on the Web. Ironically, we can use the dependencies of a chart to identify it among others.
Here, the chart is gabe565/nightscout.
At this point, we can fill in the values:
- CHART = gabe565/nightscout or oci://ghcr.io/gabe565/charts/nightscout
- CHART_NAME = nightscout
Now, we use the approach from earlier by replacing the relevant fields:
$ helm pull gabe565/nightscout --untar && helm dependency list ./nightscout && rm -rf nightscout
NAME VERSION REPOSITORY STATUS
common 1.5.1 https://bjw-s.github.io/helm-charts unpacked
mongodb 14.5.1 https://charts.bitnami.com/bitnami unpacked
Further, we can use an OCI or regular URL if the repository isn’t configured locally:
$ helm pull oci://ghcr.io/gabe565/charts/nightscout --untar && helm dependency list ./nightscout && rm -rf nightscout
Critically, our approach only lists first-level dependencies. In other words, we don’t see the dependencies of each dependency, i.e., the dependency tree.
5. Automate Installed Chart Dependency Listing
If we set several preconditions, we can automate dependency listing for an installed release:
- chart repository must be configured locally or the chart should be a part of the Artifact Hub
- chart identifier contains dash-separated name and version
- chart dependencies are non-circular
Thus, the input parameters should only include the potentially partial RELEASE_FILTER that indicates the release we want to process.
5.1. Initialization
First, we start with the usual shebang and parameter assignment:
#!/usr/bin/env bash
release_filter=$1
multilevel=$2
# global array with chart identifiers, i.e., chart_name-chart_version-repo_url strings
charts=()
In addition, we define a global charts array that holds that charts queue.
5.2. Chart Dependency Processor
After the initialization, we create the chart_deps() function, which processes the first element of the global queue and removes it:
function chart_deps() {
[ ${#charts[@]} -eq 0 ] && return
local chart_identifier="${charts[0]}"
local chart_name chart_version repo_url
local status
local deps
# get chart name and version
IFS=- read chart_name chart_version repo_url <<< "$chart_identifier"
printf 'Processing %s from %s ...\n' $chart_name $repo_url
# pull chart via URL
if [[ $repo_url =~ 'oci://' ]]; then
helm pull $repo_url --version $chart_version --untar >/dev/null 2>&1
else
helm pull $chart_name --repo $repo_url --version $chart_version --untar >/dev/null 2>&1
fi
if [ $? -ne 0 ]; then
printf 'Unable to pull %s from %s.\n\n' $chart_name $repo_url
return
fi
deps="$(helm dependency list ./$chart_name)"
printf '%s deps:\n%s\n\n' "$chart_name-$chart_version" "$deps"
if [ -n "$deps" ] && [ -n "$multilevel" ]; then
charts+=($(echo "$deps" | awk 'NR > 1 { print $1"-"$2"-"$3; }'))
fi
rm -rf $chart_name
rm -rf $chart_name-$chart_version.tgz
}
The processing comes down to parsing the input, pulling the relevant chart, and showing dependency subcommand output for it.
Further, we add any new dependencies to the end of the queue if the $multilevel argument has a value.
5.3. Main
Before we get into dependencies, we get the release filter argument and use it to extract the release chart:
# count releases from all namespaces that match the filter
releases="$(helm list --filter $release_filter --all-namespaces --no-headers
| wc --lines)"
# exit if more than one release matches
if [ $releases -ne 1 ]; then
>&2 printf 'No or multiple release matches: --filter %s.' $release_filter
exit 1
fi
# get partial chart identifier from the YAML information about the single matching release
chart_identifier="$(helm list --filter $release_filter --all-namespaces --output=yaml
| awk '/chart:/ { print $2; }')"
# extract chart name and version
IFS=- read chart_name chart_version <<< "$chart_identifier"
# get chart repository URL from repo or hub
chart_path="$(helm search repo $chart_name --version $chart_version --output yaml
| grep 'version: '$chart_version -B 1
| awk '/name:/ { print $2; }')"
if [ -n "$chart_path" ]; then
IFS=/ read repo_name chart_name <<< "$chart_path"
repo_url="$(helm repo list | awk '/'$repo_name'/ { print $2; }')"
else
repo_url="$(helm search hub $chart_name --output yaml
| grep 'version: '$chart_version -B 2
| awk 'NR == 1 && /url:/ { print $2; }')"
fi
if [ -z "$repo_url" ]; then
>&2 printf 'Repository not found for release chart %s version %s.' $chart_name $chart_version
exit 1
fi
# first chart identifier comes
chart_identifier=$chart_identifier-$repo_url
charts+=("$chart_identifier")
Once we have the chart, we search for it in the hub and any configured repositories. Once found, we place the resulting information as the first queue element.
Finally, we begin processing the queue, removing elements as we go:
while [ ${#charts[@]} -ne 0 ]; do
chart_deps
charts=("${charts[@]:1}")
done
Although not perfect, this method should list all dependencies of the provided release filter. In addition, it can try to list dependencies of dependencies.
If required, the method can be adjusted to work for any chart, not only release charts.
6. Summary
In this article, we talked about dependency checks via Helm.
In conclusion, although no native feature exists, we can use the Helm functions and shell scripting to implement ways to list the dependencies of an installed chart.