Files
headlamp-polaris-plugin/scripts/deploy-plugin-via-installer.sh
T
Gandalf the Greybeard 233b93cfdf fix: extract cluster from URL path in AppBar badge (useCluster returns null outside cluster routes)
useCluster() returns null when called from AppBar context because the
component renders outside the cluster route hierarchy. Parse the cluster
name from location.pathname instead, with useCluster() as fallback.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-16 11:34:33 +00:00

179 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# deploy-plugin-via-installer.sh
#
# Deploys the Headlamp Polaris plugin to a running Headlamp instance using
# the Headlamp plugin installer (pluginsManager sidecar) with Artifact Hub
# as the sole distribution channel.
#
# This script:
# 1. Verifies Headlamp connectivity
# 2. Ensures the HelmRelease has pluginsManager configured with the
# Artifact Hub source for the polaris plugin
# 3. Waits for the plugin to appear in the Headlamp /plugins endpoint
# 4. Validates plugin availability
#
# Requirements:
# - kubectl configured with cluster access
# - HEADLAMP_URL environment variable (defaults to in-cluster service)
#
# Usage:
# ./scripts/deploy-plugin-via-installer.sh
#
# Per INSTALLATION_POLICY.md: Artifact Hub is the ONLY approved distribution
# channel. No kubectl exec/cp, no init containers, no manual tarball extraction.
set -euo pipefail
HEADLAMP_URL="${HEADLAMP_URL:-http://headlamp.kube-system.svc.cluster.local}"
HEADLAMP_NAMESPACE="${HEADLAMP_NAMESPACE:-kube-system}"
HEADLAMP_RELEASE="${HEADLAMP_RELEASE:-headlamp}"
ARTIFACTHUB_SOURCE="https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin"
PLUGIN_NAME="headlamp-polaris"
POLL_INTERVAL=5
POLL_TIMEOUT=120
log() { echo "[deploy-plugin] $*"; }
die() { log "ERROR: $*" >&2; exit 1; }
# ── Step 1: Verify Headlamp connectivity ──────────────────────────────────────
log "Checking Headlamp at $HEADLAMP_URL ..."
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "$HEADLAMP_URL" || echo "000")
if [ "$HTTP_CODE" = "000" ]; then
die "Cannot reach Headlamp at $HEADLAMP_URL"
fi
log "Headlamp responded HTTP $HTTP_CODE"
# ── Step 2: Check current plugin state ────────────────────────────────────────
log "Querying installed plugins ..."
PLUGINS_JSON=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
PLUGIN_FOUND=$(echo "$PLUGINS_JSON" | node -e "
const plugins = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const found = plugins.find(p =>
p.name === '$PLUGIN_NAME' ||
p.name === 'polaris' ||
p.name.includes('polaris')
);
if (found) {
console.log(JSON.stringify({ name: found.name, version: found.version || 'unknown' }));
} else {
console.log('null');
}
" 2>/dev/null || echo "null")
if [ "$PLUGIN_FOUND" != "null" ]; then
log "Plugin already installed: $PLUGIN_FOUND"
DEPLOYED_NAME=$(echo "$PLUGIN_FOUND" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).name" 2>/dev/null || echo "unknown")
log "Deployed plugin directory name: $DEPLOYED_NAME"
exit 0
fi
log "Plugin not found — ensuring pluginsManager is configured ..."
# ── Step 3: Configure pluginsManager via HelmRelease ──────────────────────────
# Check if this is a Flux HelmRelease or standalone Helm release
HELMRELEASE_EXISTS=$(kubectl get helmrelease "$HEADLAMP_RELEASE" -n "$HEADLAMP_NAMESPACE" -o name 2>/dev/null || echo "")
if [ -n "$HELMRELEASE_EXISTS" ]; then
log "Found Flux HelmRelease: $HELMRELEASE_EXISTS"
# Check if pluginsManager is already configured
PM_ENABLED=$(kubectl get helmrelease "$HEADLAMP_RELEASE" -n "$HEADLAMP_NAMESPACE" \
-o jsonpath='{.spec.values.pluginsManager.enabled}' 2>/dev/null || echo "")
if [ "$PM_ENABLED" != "true" ]; then
log "Patching HelmRelease to enable pluginsManager with Artifact Hub source ..."
kubectl patch helmrelease "$HEADLAMP_RELEASE" -n "$HEADLAMP_NAMESPACE" --type merge -p "
spec:
values:
pluginsManager:
enabled: true
configContent: |
plugins:
- name: $PLUGIN_NAME
source: $ARTIFACTHUB_SOURCE
" || die "Failed to patch HelmRelease"
log "HelmRelease patched — Flux will reconcile the deployment"
else
log "pluginsManager already enabled — checking plugin config ..."
# Verify polaris is in the config
CONFIG_CONTENT=$(kubectl get helmrelease "$HEADLAMP_RELEASE" -n "$HEADLAMP_NAMESPACE" \
-o jsonpath='{.spec.values.pluginsManager.configContent}' 2>/dev/null || echo "")
if ! echo "$CONFIG_CONTENT" | grep -q "polaris"; then
log "Adding polaris plugin to pluginsManager config ..."
UPDATED_CONFIG="${CONFIG_CONTENT}
- name: $PLUGIN_NAME
source: $ARTIFACTHUB_SOURCE"
kubectl patch helmrelease "$HEADLAMP_RELEASE" -n "$HEADLAMP_NAMESPACE" --type merge -p "
spec:
values:
pluginsManager:
configContent: |
$(echo "$UPDATED_CONFIG" | sed 's/^/ /')
" || die "Failed to update pluginsManager config"
log "Polaris plugin added to pluginsManager config"
else
log "Polaris plugin already in pluginsManager config"
fi
fi
else
# Standalone Helm release — use helm upgrade
log "No Flux HelmRelease found — checking for standalone Helm release ..."
HELM_RELEASE=$(helm list -n "$HEADLAMP_NAMESPACE" -q --filter "$HEADLAMP_RELEASE" 2>/dev/null || echo "")
if [ -n "$HELM_RELEASE" ]; then
log "Found Helm release: $HELM_RELEASE — upgrading with pluginsManager ..."
helm upgrade "$HEADLAMP_RELEASE" headlamp/headlamp -n "$HEADLAMP_NAMESPACE" --reuse-values \
--set pluginsManager.enabled=true \
--set-string "pluginsManager.configContent=plugins:\n - name: $PLUGIN_NAME\n source: $ARTIFACTHUB_SOURCE\n" \
|| die "helm upgrade failed"
log "Helm release upgraded"
else
die "No Headlamp deployment found (checked Flux HelmRelease and Helm release in $HEADLAMP_NAMESPACE)"
fi
fi
# ── Step 4: Wait for plugin to become available ───────────────────────────────
log "Waiting for plugin to appear (timeout: ${POLL_TIMEOUT}s) ..."
ELAPSED=0
while [ "$ELAPSED" -lt "$POLL_TIMEOUT" ]; do
PLUGINS_JSON=$(curl -sf --connect-timeout 5 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
FOUND=$(echo "$PLUGINS_JSON" | node -e "
const plugins = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const found = plugins.find(p =>
p.name === '$PLUGIN_NAME' ||
p.name === 'polaris' ||
p.name.includes('polaris')
);
console.log(found ? 'yes' : 'no');
" 2>/dev/null || echo "no")
if [ "$FOUND" = "yes" ]; then
log "Plugin is now available!"
echo "$PLUGINS_JSON" | node -e "
const plugins = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const found = plugins.find(p =>
p.name === '$PLUGIN_NAME' ||
p.name === 'polaris' ||
p.name.includes('polaris')
);
console.log('[deploy-plugin] Plugin: ' + found.name + '@' + (found.version || 'unknown'));
" 2>/dev/null
exit 0
fi
sleep "$POLL_INTERVAL"
ELAPSED=$((ELAPSED + POLL_INTERVAL))
log "Waiting... (${ELAPSED}s / ${POLL_TIMEOUT}s)"
done
die "Timed out waiting for plugin to appear after ${POLL_TIMEOUT}s"