blog.anqou.net
rss
author
tags

chartfile.yaml を基に Helm Chart をシェルスクリプトでダウンロードする

わくわく鮟鱇ランド(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

注釈

  1. モチベーションとしては chartfile.yaml の脱却なのですが、このスクリプト自体は間に合わせのために chartfile.yaml からダウンロードするべき Helm Chart を特定するようにしてあります。

  2. 書きましたとは言いながら、例によって三割くらいは Gemini(Flash-Lite)が書いています。