わくわく鮟鱇ランド(mstdn.anqou.net)をデプロイしている Kubernetes クラスタのマニフェストは Grafana Tanka を使って管理しています。Grafana Tanka では chartfile.yaml というファイルに以下のようなフォーマットで使いたい Helm Chart の情報を書くことで、Jsonnet から Helm Chart を render できるようになります:
directory: charts
repositories:
- name: grafana
url: https://grafana.github.io/helm-charts
- name: forgejo-helm
url: oci://code.forgejo.org/forgejo-helm
requires:
- chart: grafana/loki
version: 7.0.0
- chart: forgejo-helm/forgejo
version: 17.1.1
version: 1
Grafana Tanka が提供する tk コマンドをインストールした上で tk tool charts vendor --prune を実行すると、上記の YAML ファイルを参照して Helm Chart が charts/ ディレクトリ配下にダウンロードされます。その後 tk export を実行するとダウンロードされた Helm Chart が使用されます。
tk コマンドは nixpkgs でも提供されているので flake.nix 経由で tk が入った dev shell を作り便利に使っていたのですが、最近 nix flake update によって上記の仕組みが壊れました。具体的には nixpkgs の revision が a799d3e3886da994fa307f817a6bc705ae538eeb から 9ae611a455b90cf061d8f332b977e387bda8e1ca に上がったタイミングで tk tool charts vendor --prune が以下のようなエラーを出して失敗するようになりました:
{"level":"info","time":"2026-06-14T22:37:04+09:00","message":"Vendoring..."}
{"level":"info","time":"2026-06-14T22:37:04+09:00","message":"Syncing Repositories ..."}
Error: Error: failed to update the following repositories: [oci://code.forgejo.org/forgejo-helm]
Nixpkgs のコミット履歴を二分探索して原因を突き止めても良かったのですが、そもそも chartfile.yaml 自体が Renovate と相性が悪いこともあり、この仕組みを辞めたいと思っていたので、シェルスクリプトを書いて tk tool charts vendor --prune 相当の処理を自前で行うことにしました。つまり chartfile.yaml 相当の入力を受け取り、そこで指定されている Helm Chart をダウンロードしてきて charts/ ディレクトリに配置するようなスクリプトです。
実は helm コマンドには helm pull というそのものな機能があるので、初めはこれを使えばすぐにできると思っていました。ただ helm pull で HTTPS で配布されている Helm Chart をダウンロードしてくるには helm repo add が必須で面倒だったので、結局自前で index.yaml をパーズする下記のようなスクリプトを書きました[1][2]。
#!/usr/bin/env bash
set -eu -o pipefail
OUTDIR=charts
mkdir -p "${OUTDIR}"
# Extract required charts. Each line should be in the following format:
# https://victoriametrics.github.io/helm-charts victoria-metrics-k8s-stack 0.82.0
REQUIRES=$( \
cat chartfile.yaml | \
yq -o=json . | \
jq -r '
.repositories as $repos |
.requires[] |
(.chart | split("/")) as $parts |
.version as $v |
($repos | map(select(.name == $parts[0])) | .[0].url) as $repo_url |
[$repo_url, $parts[1], $v] |
@tsv' \
)
echo "$REQUIRES" | while read -r repo chart version; do
# Skip the chart if it's already downloaded
if [ "$(cat "${OUTDIR}/${chart}/Chart.yaml" 2>/dev/null | yq ".version")" = "${version}" ]; then
continue
fi
echo "Processing ${repo} ${chart} ${version}"
# Download the compressed chart
chart_tgz_filepath="${OUTDIR}/${chart}-${version}.tgz"
if echo "$repo" | grep -E "^https:" > /dev/null; then
chart_url=$(curl -sSLf "$repo/index.yaml" | \
yq ".entries.\"${chart}\"[] | select(.version == \"${version}\") | .urls[0]")
if ! echo "${chart_url}" | grep -E "^https:" > /dev/null; then
chart_url="${repo}/${chart_url}"
fi
wget -q -O "${chart_tgz_filepath}" "${chart_url}"
elif echo "$repo" | grep -E "^oci:"; then
helm pull "${repo}/${chart}" --version "${version}" --destination "${OUTDIR}"
fi
# Uncompress the chart
tar xf "${chart_tgz_filepath}" -C "${OUTDIR}"
# Cleanup
rm "${chart_tgz_filepath}"
done