#!/usr/bin/env bash set -euo pipefail # Package and push all Helm charts found under the repo # Required env: # HELM_OCI_NAMESPACE e.g. ghcr.io/OWNER or gitea.example.com/OWNER # Optional env: # HELM_USERNAME / HELM_PASSWORD for registry login # HELM_LOGIN_EXTRA_ARGS: extra flags for `helm registry login` (e.g., --insecure --plain-http) # CHART_DIRS: space-separated list of chart directories; if empty, auto-discover # DRY_RUN=1: only package, do not push # HELM_PUSH_EXTRA_ARGS: extra flags for `helm push` (e.g., --insecure-skip-tls-verify --plain-http) # ALLOW_OVERWRITE=1: delete existing chart version in Harbor/OCI before push (OCI tags are immutable) ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT_DIR" if [[ -z "${HELM_OCI_NAMESPACE:-}" ]]; then echo "[helm_publish] HELM_OCI_NAMESPACE not set (e.g., ghcr.io/owner or gitea.example.com/owner)." >&2 exit 1 fi # Derive registry host and project from HELM_OCI_NAMESPACE HELM_REGISTRY_HOST="${HELM_OCI_NAMESPACE%%/*}" HELM_REGISTRY_PROJECT="${HELM_OCI_NAMESPACE#*/}" if [[ -z "$HELM_REGISTRY_PROJECT" || "$HELM_REGISTRY_PROJECT" == "$HELM_OCI_NAMESPACE" ]]; then echo "[helm_publish] Invalid HELM_OCI_NAMESPACE: expected host/project, got '$HELM_OCI_NAMESPACE'" >&2 exit 1 fi # Infer API scheme/insecure flags for Harbor API calls API_SCHEME="https" if [[ "${HELM_LOGIN_EXTRA_ARGS:-}${HELM_PUSH_EXTRA_ARGS:-}" == *"--plain-http"* ]]; then API_SCHEME="http" fi INSECURE_CURL_FLAG="" if [[ "${HELM_LOGIN_EXTRA_ARGS:-}${HELM_PUSH_EXTRA_ARGS:-}" == *"--insecure"* || "${HELM_PUSH_EXTRA_ARGS:-}" == *"--insecure-skip-tls-verify"* ]]; then INSECURE_CURL_FLAG="-k" fi # Auto-discover charts when CHART_DIRS not provided if [[ -z "${CHART_DIRS:-}" ]]; then mapfile -t chart_paths < <(find . -maxdepth 2 -type f -name Chart.yaml -not -path "*/charts/*" | sort) if [[ ${#chart_paths[@]} -eq 0 ]]; then echo "[helm_publish] No charts found (no Chart.yaml)." >&2 exit 1 fi CHART_DIRS="" for p in "${chart_paths[@]}"; do d="$(dirname "$p")" CHART_DIRS+=" ${d#./}" done fi # Login if credentials present if [[ -n "${HELM_USERNAME:-}" && -n "${HELM_PASSWORD:-}" ]]; then echo "[helm_publish] Logging into registry ${HELM_REGISTRY_HOST} as ${HELM_USERNAME}" # shellcheck disable=SC2086 helm registry login ${HELM_LOGIN_EXTRA_ARGS:-} "$HELM_REGISTRY_HOST" -u "$HELM_USERNAME" -p "$HELM_PASSWORD" else echo "[helm_publish] HELM_USERNAME/HELM_PASSWORD not set; assuming registry creds already configured" fi status=0 for chart_dir in ${CHART_DIRS}; do if [[ ! -f "$chart_dir/Chart.yaml" ]]; then echo "[helm_publish] Skip $chart_dir (no Chart.yaml)" continue fi echo "[helm_publish] Processing chart: $chart_dir" # Ensure dependencies are built if [[ -f "$chart_dir/Chart.yaml" ]]; then helm dependency build "$chart_dir" || true fi pkg_out_dir="$chart_dir/.packages" mkdir -p "$pkg_out_dir" # Lint chart (non-fatal) if ! helm lint "$chart_dir"; then echo "[helm_publish] Warning: helm lint failed for $chart_dir" fi # Package chart pkg_path=$(helm package "$chart_dir" --destination "$pkg_out_dir" | awk '{print $NF}') if [[ ! -f "$pkg_path" ]]; then echo "[helm_publish] Failed to package $chart_dir" >&2 status=1 continue fi echo "[helm_publish] Packaged: $pkg_path" if [[ "${DRY_RUN:-}" == "1" ]]; then echo "[helm_publish] DRY_RUN enabled; skip push for $pkg_path" continue fi # Resolve chart name/version from Chart.yaml chart_name=$(sed -n 's/^name:[[:space:]]*\(.*\)$/\1/p' "$chart_dir/Chart.yaml" | head -n1 | tr -d '"' | xargs || true) chart_version=$(sed -n 's/^version:[[:space:]]*\(.*\)$/\1/p' "$chart_dir/Chart.yaml" | head -n1 | tr -d '"' | xargs || true) # Optional pre-delete to allow overwrite of existing tag in Harbor if [[ "${ALLOW_OVERWRITE:-}" == "1" && -n "${chart_name}" && -n "${chart_version}" ]]; then if [[ -n "${HELM_USERNAME:-}" && -n "${HELM_PASSWORD:-}" ]]; then del_url="${API_SCHEME}://${HELM_REGISTRY_HOST}/api/v2.0/projects/${HELM_REGISTRY_PROJECT}/repositories/${chart_name}/artifacts/${chart_version}" echo "[helm_publish] Attempting delete (if exists): $del_url" http_code=$(curl -sS ${INSECURE_CURL_FLAG} -u "${HELM_USERNAME}:${HELM_PASSWORD}" -o /dev/null -w "%{http_code}" -X DELETE "$del_url" || true) case "$http_code" in 200|202|404) echo "[helm_publish] Delete HTTP $http_code (ok)";; "") echo "[helm_publish] Warning: curl returned no status; continuing";; *) echo "[helm_publish] Warning: delete returned HTTP $http_code; continuing to push";; esac else echo "[helm_publish] ALLOW_OVERWRITE=1 but HELM_USERNAME/HELM_PASSWORD not set; skip delete" fi fi # Push to OCI registry; Helm will use chart name from the package echo "[helm_publish] Pushing $pkg_path to oci://$HELM_OCI_NAMESPACE" if ! helm push ${HELM_PUSH_EXTRA_ARGS:-} "$pkg_path" "oci://$HELM_OCI_NAMESPACE"; then echo "[helm_publish] Push failed for $pkg_path" >&2 status=1 fi done exit $status