fix: match plugin settings name to deploy dir + fix badge nav URL #55

Closed
ghost wants to merge 3 commits from fix/e2e-settings-name-and-badge-url into main
5 changed files with 204 additions and 101 deletions
Showing only changes of commit 233b93cfdf - Show all commits
+17 -16
View File
@@ -25,23 +25,24 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Preflight — verify Headlamp and plugin version
- name: Deploy plugin via Headlamp plugin installer
env:
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
HEADLAMP_NAMESPACE: ${{ vars.HEADLAMP_NAMESPACE || 'kube-system' }}
HEADLAMP_RELEASE: ${{ vars.HEADLAMP_RELEASE || 'headlamp' }}
run: |
chmod +x scripts/deploy-plugin-via-installer.sh
./scripts/deploy-plugin-via-installer.sh
- name: Preflight — verify plugin version
env:
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
run: |
EXPECTED=$(node -p "require('./package.json').version")
PLUGIN_NAME=$(node -p "require('./package.json').artifacthub?.name || require('./package.json').name")
PLUGIN_NAME=$(node -p "require('./package.json').name")
echo "Expected: $PLUGIN_NAME@$EXPECTED"
# Check Headlamp connectivity
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "$HEADLAMP_URL" || true)
if [ "$HTTP_CODE" = "000" ]; then
echo "::error::Cannot reach Headlamp at $HEADLAMP_URL"
exit 1
fi
echo "Headlamp responded HTTP $HTTP_CODE"
# Check installed plugins and version match
# List installed plugins
PLUGIN_JSON=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
node -e "
const expected = '$EXPECTED';
@@ -51,18 +52,18 @@ jobs:
for (const p of plugins) console.log(' ' + p.name + '@' + (p.version||'unknown'));
const ours = plugins.find(p => p.name === pluginName || p.name === 'polaris' || p.name.includes('polaris'));
if (!ours) {
console.log('::warning::Plugin ' + pluginName + ' not found in Headlamp — data-dependent tests will fail');
} else {
console.log('Found plugin: ' + ours.name + ' at path ' + ours.path);
console.log('::error::Plugin not found after deploy — E2E tests will fail');
process.exit(1);
}
console.log('Found plugin: ' + ours.name + ' at path ' + ours.path);
" "$PLUGIN_JSON"
# Fetch deployed plugin version from package.json
# Check version match (warn only — Artifact Hub may lag behind releases)
DEPLOYED_VERSION=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins/$PLUGIN_NAME/package.json" 2>/dev/null \
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "unknown")
echo "Deployed version: $DEPLOYED_VERSION"
if [ "$DEPLOYED_VERSION" != "$EXPECTED" ] && [ "$DEPLOYED_VERSION" != "unknown" ]; then
echo "::warning::Version mismatch — repo has $EXPECTED but Headlamp runs $DEPLOYED_VERSION. Tests may fail due to stale plugin."
echo "::warning::Version mismatch — repo has $EXPECTED but Headlamp runs $DEPLOYED_VERSION. Artifact Hub may not have synced yet."
fi
- name: Install Playwright browsers
@@ -1,83 +0,0 @@
---
# Custom Headlamp values for static plugin installation
# This disables the plugin manager and uses an init container instead
# Disable the plugin manager sidecar
pluginsManager:
enabled: false
# Use an init container to install plugins to /headlamp/static-plugins
initContainers:
- name: install-plugins
image: node:lts-alpine
command:
- /bin/sh
- -c
- |
set -e
echo "Installing plugins to /headlamp/static-plugins..."
# Create plugins directory
mkdir -p /headlamp/static-plugins
# Set up npm cache
export NPM_CONFIG_CACHE=/tmp/npm-cache
export NPM_CONFIG_USERCONFIG=/tmp/npm-userconfig
mkdir -p /tmp/npm-cache /tmp/npm-userconfig
# Install polaris plugin
echo "Installing polaris plugin..."
cd /headlamp/static-plugins
npm pack headlamp-polaris-plugin@0.3.0
tar -xzf headlamp-polaris-plugin-0.3.0.tgz
mv package headlamp-polaris-plugin
rm headlamp-polaris-plugin-0.3.0.tgz
# Install other plugins
npx --yes @headlamp-k8s/plugin@latest install \
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_flux \
--folderName /headlamp/static-plugins
npx --yes @headlamp-k8s/plugin@latest install \
--source https://artifacthub.io/packages/headlamp/headlamp-trivy/headlamp_trivy \
--folderName /headlamp/static-plugins
npx --yes @headlamp-k8s/plugin@latest install \
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_cert-manager \
--folderName /headlamp/static-plugins
npx --yes @headlamp-k8s/plugin@latest install \
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_ai_assistant \
--folderName /headlamp/static-plugins
echo "All plugins installed successfully"
ls -la /headlamp/static-plugins
securityContext:
runAsUser: 100
runAsGroup: 101
runAsNonRoot: true
privileged: false
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
volumeMounts:
- name: static-plugins
mountPath: /headlamp/static-plugins
# Configure headlamp to use static plugins
config:
pluginsDir: /headlamp/static-plugins
# Add volume for static plugins
volumes:
- name: static-plugins
emptyDir: {}
# Add volume mount to main container
volumeMounts:
- name: static-plugins
mountPath: /headlamp/static-plugins
readOnly: true
+178
View File
@@ -0,0 +1,178 @@
#!/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"
+1
View File
@@ -29,6 +29,7 @@ vi.mock('@mui/material/styles', () => ({
const mockPush = vi.fn();
vi.mock('react-router-dom', () => ({
useHistory: () => ({ push: mockPush }),
useLocation: () => ({ pathname: '/c/test-cluster/some-page', search: '', hash: '' }),
}));
const mockUsePolarisDataContext = vi.fn();
+8 -2
View File
@@ -1,7 +1,7 @@
import { K8s } from '@kinvolk/headlamp-plugin/lib';
import { useTheme } from '@mui/material/styles';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { useHistory, useLocation } from 'react-router-dom';
import { computeScore, countResults } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
@@ -13,7 +13,13 @@ export default function AppBarScoreBadge() {
const theme = useTheme();
const { data, loading } = usePolarisDataContext();
const history = useHistory();
const cluster = K8s.useCluster();
const location = useLocation();
const clusterFromHook = K8s.useCluster();
// useCluster() returns null in AppBar context (outside cluster routes),
// so extract cluster from the current URL path as primary source.
const clusterMatch = location.pathname.match(/^\/c\/([^/]+)/);
const cluster = clusterMatch ? clusterMatch[1] : clusterFromHook;
if (loading || !data) {
return null; // Graceful degradation when Polaris unavailable