Add CONTEXT.md as a comprehensive reverse prompt for AI assistants working on this project. Consolidates knowledge from CLAUDE.md and MEMORY.md into a single, structured reference document. Includes: - Project overview and architecture - Technology constraints and component patterns - Development workflow and testing strategy - CI/CD and release process - Known issues and workarounds (corrects outdated watchPlugins info) - Deployment patterns and RBAC requirements - Quick reference commands and diagnosis guide - Historical context for key decisions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
18 KiB
CONTEXT.md - Headlamp Polaris Plugin
Purpose: Comprehensive reverse prompt for AI assistants working on this project.
Project Overview
The Headlamp Polaris Plugin surfaces Fairwinds Polaris audit results directly inside the Headlamp Kubernetes UI. It provides a read-only dashboard showing cluster-wide security, reliability, and efficiency scores derived from Polaris policy checks.
- Stack: React + TypeScript plugin for Headlamp (v0.26+)
- Data Source: Polaris dashboard API via Kubernetes service proxy (read-only)
- Current Version: v0.4.1
- Key Constraint: No direct Kubernetes resource access - all data fetched through service proxy
Architecture & Data Flow
Component Hierarchy
src/index.tsx # Entry point: registers routes, sidebar, settings
├── PolarisDataContext.tsx # Shared data fetch with auto-refresh
├── components/
│ ├── DashboardView.tsx # Overview (score, checks, top issues)
│ ├── NamespacesListView.tsx # Namespace list with scores
│ ├── NamespaceDetailView.tsx # Per-namespace drill-down (drawer)
│ ├── PolarisSettings.tsx # Settings (refresh interval, URL, test)
│ ├── AppBarScoreBadge.tsx # Cluster score badge in top nav
│ └── InlineAuditSection.tsx # Injected into workload detail views
└── api/
└── polaris.ts # Types, hooks, utilities
Data Source
- Service Proxy Path:
/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json - Schema:
AuditDatawithClusterInfo,Results[]containing nestedPodResultandContainerResults - Method:
ApiProxy.request()from Headlamp plugin SDK (handles K8s API auth automatically)
State Management
- Pattern: React Context (see
src/api/PolarisDataContext.tsx) - Rationale: ADR-001 - Prevents duplicate API calls when multiple components need same data
- Auto-refresh: User-configurable interval (1/5/10/30 min, default 5 min)
- Storage: Refresh interval and dashboard URL stored in
localStorage
Score Computation
// Formula: (pass / total) * 100, rounded to nearest integer
function computeScore(counts: ResultCounts): number {
if (counts.total === 0) return 0;
return Math.round((counts.pass / counts.total) * 100);
}
Technology Constraints
⚠️ CRITICAL: Headlamp Components Only
MUST use @kinvolk/headlamp-plugin/lib/CommonComponents
NEVER import from @mui/material or @mui/icons-material
Why: Historical issue (v0.3.2) - MUI imports caused plugin load failures. Headlamp provides all needed components as re-exports.
// ✅ Correct
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
// ❌ Wrong - will break plugin
import { Box, Chip } from '@mui/material';
Other Constraints
- TypeScript Strictness: No
any, explicit types, strict mode enabled - Packaging:
@kinvolk/headlamp-pluginis peer dependency - don't bundle React/MUI - Theme Handling: Use CSS variables (
--mui-palette-*), not theme imports - Sidebar Limitation: Headlamp only supports 2-level nesting (parent → children)
Component Patterns & Gotchas
Headlamp Component Issues
-
StatusLabel with empty status
// ❌ Renders near-invisible (muted background) <StatusLabel status="">{value}</StatusLabel> // ✅ Use plain String() for neutral values <span>{String(value)}</span> -
Link component crashes on plugin routes
// ❌ Headlamp Link crashes on plugin-registered routes import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; // ✅ Use react-router-dom Link with Router.createRouteURL import { Link } from 'react-router-dom'; import { Router } from '@kinvolk/headlamp-plugin/lib'; <Link to={Router.createRouteURL('/polaris/namespaces')}>View</Link> -
Visual components that work well
PercentageCircle- Great for score displayPercentageBar- Great for check distributionSimpleTable- Fast, clean tablesNameValueTable- Key-value pairsSectionBox- Card containers with titles
Code Conventions
- Functional Components: Always use function components with hooks
- Named Exports: Prefer named exports over default exports
- Props Interfaces: Define as TypeScript interfaces, not inline types
- Import Order: React → third-party → Headlamp → local (auto-sorted by eslint)
RBAC & Security
Minimal Permission Required
The plugin requires only this RBAC permission:
| Verb | API Group | Resource | Resource Name | Namespace |
|---|---|---|---|---|
get |
"" (core) |
services/proxy |
polaris-dashboard |
polaris |
Example Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
Security Notes
- Namespaced Role: MUST be namespaced Role, NOT ClusterRole
- ResourceNames Required: Always specify
resourceNames: ["polaris-dashboard"] - No Write Operations: Plugin only performs GET, never create/update/delete
- Token-Auth Mode: When Headlamp uses user tokens, each user needs the RoleBinding
- Network Policy: If enforced, allow API server →
polaris-dashboard:80ingress - Audit Logging: Every proxy request logged as K8s API audit event
Development Workflow
Commands
# Install dependencies
npm install
# Start development mode (hot reload at localhost:4466)
npm start
# Build plugin
npm run build
# Create tarball for distribution
npm run package
# Type-check without emitting
npm run tsc
# Lint
npm run lint
# Run unit tests
npm test
# Run E2E tests (requires cluster access)
npm run e2e
# Format code
npm run format
# Check formatting (CI)
npm run format:check
Branching Strategy
- ✅ ALWAYS use feature branches for code changes (
feat/*,fix/*,docs/*) - ✅ MAY push directly to main for: documentation-only changes, version bump commits
- ❌ NEVER push code changes directly to main
Commit Convention
Use Conventional Commits:
feat:- New featurefix:- Bug fixdocs:- Documentation onlychore:- Maintenance (deps, config)test:- Test changesci:- CI/CD changes
PR Process
All PRs must pass:
- Build (
npm run build) - Lint (
npm run lint) - Type-check (
npm run tsc) - Unit tests (
npm test) - Format check (
npm run format:check)
Before committing: Always run npx prettier --write src/
Testing Strategy
Unit Tests (Vitest)
npm test # Run once
npm run test:watch # Watch mode
- Framework: Vitest with jsdom environment
- Test files:
*.test.ts,*.test.tsxinsrc/ - Setup:
vitest.setup.tswith@testing-library/jest-dom - Coverage: Focus on meaningful tests, not just numbers
- Test utilities:
src/test-utils.tsxprovides test wrapper with context
E2E Tests (Playwright)
npm run e2e # Headless
npm run e2e:headed # With browser UI
- Framework: Playwright
- Test files:
e2e/*.spec.tspolaris.spec.ts- Sidebar, overview, namespaces, detail drawersettings.spec.ts- Plugin settings pageappbar.spec.ts- App bar score badge
- Auth: Supports both OIDC (Authentik) and token-based auth (see
e2e/auth.setup.ts) - CI: Runs on GitHub Actions with
k3s-animaniacsrunner
Local E2E Setup
# Token-based auth
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
npm run e2e
# OIDC auth (Authentik)
export AUTHENTIK_USERNAME=your-username
export AUTHENTIK_PASSWORD=your-password
npm run e2e
CI/CD & Release
CI Workflow (.github/workflows/ci.yaml)
Runs on push to main and all PRs:
- Checkout
npm cinpm run buildnpm run lintnpm run tscnpm run format:checknpm test
Runner: local-ubuntu-latest
E2E Workflow (.github/workflows/e2e.yaml)
Runs on push, PR, and manual trigger:
- Checkout
npm cinpm run e2e
Runner: k3s-animaniacs (has cluster access)
Requires: HEADLAMP_URL, HEADLAMP_TOKEN or AUTHENTIK_USERNAME/AUTHENTIK_PASSWORD
Release Workflow (.github/workflows/release.yaml)
Manual trigger via workflow_dispatch with version input:
# Via GitHub UI or CLI
gh workflow run release.yaml -f version=0.4.2
Steps:
- Validate version format (semver)
- Bump
package.json+artifacthub-pkg.yml - Build plugin
- Package tarball
- Compute SHA256 checksum
- Commit version bump
- Create git tag
- Create GitHub release
- Upload tarball to release
Guard: Skips if checksum already matches (prevents infinite loop)
Post-release: ArtifactHub pulls metadata every 30 min (no webhook, pull-based)
Version Bump Requirements
ALWAYS bump both files in the same commit:
package.json-versionfieldartifacthub-pkg.yml-versionfield +digest(checksum) +archive.url
Known Issues & Workarounds
⚠️ Headlamp v0.39.0 Known Issues
AutoSizer JavaScript Error
- Symptom: Console shows
TypeError: undefined is not an object (evaluating 'io.AutoSizer') - Impact: Cosmetic error in Settings page, doesn't break functionality
- Root Cause: Headlamp core bug, not plugin-related
- Workaround: None needed, can be ignored
Plugin Loading (RESOLVED)
- Old Issue: Previously thought
config.watchPlugins: falsewas required - Resolution: Plugins load correctly with default
watchPlugins: true - Note: If you see old docs mentioning
watchPlugins: false, ignore them
Polaris Dashboard Behavior
Stale Audit Data
- Symptom: Plugin shows old audit timestamp
- Root Cause: Polaris dashboard runs audit once at pod startup, then caches results
- Does NOT: Continuously re-audit in real-time
- Workaround: Restart Polaris pods for fresh data
kubectl rollout restart deployment -n polaris polaris-dashboard - Load Balancing: Service balances across multiple pods - each may have different audit timestamps
- Plugin Auto-Refresh: Works correctly - just fetches whatever Polaris currently has cached
Skipped Count Limitation
What It Shows:
- Only checks with
Severity: "ignore"in Polaris API response - Does NOT include annotation-based exemptions (
polaris.fairwinds.com/*-exempt)
Why:
- Polaris omits exempted checks from
results.json - Plugin has no access to raw K8s resources to compute exemptions
- By design: service proxy limitation
Workaround:
- Link to native Polaris dashboard for full exemption count
- UI tooltip explains this limitation
Deployment Patterns
Plugin Manager (Recommended)
Install via Headlamp UI (Settings → Plugins → Catalog) or Helm values:
pluginsManager:
enabled: true
configContent: |
plugins:
- name: polaris
source: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
Sidecar Container (Alternative)
spec:
containers:
- name: headlamp
# ... main container
- name: headlamp-plugin
image: node:lts-alpine
command:
- /bin/sh
- -c
- |
npx @headlamp-k8s/pluginctl@latest install \
--config /config/plugin.yml \
--folderName /headlamp/plugins \
--watch
volumeMounts:
- name: plugins-dir
mountPath: /headlamp/plugins
- name: plugin-config
mountPath: /config
volumes:
- name: plugins-dir
emptyDir: {}
- name: plugin-config
configMap:
name: headlamp-plugin-config
Manual Tarball
# Download release
wget https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.4.1/headlamp-polaris-plugin-0.4.1.tgz
# Extract to plugin directory
tar -xzf headlamp-polaris-plugin-0.4.1.tgz -C /headlamp/plugins/
# Restart Headlamp
kubectl rollout restart deployment headlamp -n kube-system
Project Files Reference
src/
index.tsx # Entry point: registers sidebar, routes, settings, etc.
api/
polaris.ts # Core types, usePolarisData hook, utilities
PolarisDataContext.tsx # React Context provider for shared data
components/
DashboardView.tsx # Overview page (score, checks, top issues)
NamespacesListView.tsx # Namespace table with scores
NamespaceDetailView.tsx # Drawer panel with per-namespace drill-down
PolarisSettings.tsx # Settings page (refresh, URL, test)
AppBarScoreBadge.tsx # Cluster score chip in top nav bar
InlineAuditSection.tsx # Injected into resource detail views
test-utils.tsx # Test helpers (wrapper with context)
.github/workflows/
ci.yaml # Lint, type-check, build, test
e2e.yaml # Playwright E2E tests
release.yaml # Automated releases
e2e/ # Playwright tests
polaris.spec.ts # Main plugin functionality
settings.spec.ts # Settings page
appbar.spec.ts # App bar badge
auth.setup.ts # OIDC/token auth setup
docs/ # Comprehensive documentation
architecture/ # Overview, design decisions, ADRs
deployment/ # Helm, Kubernetes, production guides
troubleshooting/ # Common issues, RBAC, network problems
getting-started/ # Quick start, prerequisites, installation
package.json # Version, scripts, dependencies
artifacthub-pkg.yml # ArtifactHub metadata (version, checksum)
tsconfig.json # Extends @kinvolk/headlamp-plugin config
vitest.config.mts # Vitest config (jsdom, excludes e2e/)
.eslintrc.js # Extends @headlamp-k8s/eslint-config
.prettierrc.js # Uses @headlamp-k8s prettier config
MCP Servers (Claude Code)
- GitHub: Source control (
github-mcp-server), repo atcpfarhood/headlamp-polaris-plugin - Kubernetes (local): Cluster access via
kubernetes-mcp-server - Flux (local): Flux Operator access via
flux-operator-mcp - Playwright: Browser automation via
@playwright/mcp
Common Tasks Quick Reference
# Start development
npm install && npm start
# Run all checks before PR
npm run build && npm run lint && npm run tsc && npm test && npm run format
# Create release (maintainers only)
# 1. Edit CHANGELOG.md
# 2. Trigger release workflow:
gh workflow run release.yaml -f version=0.4.2
# Run E2E tests locally
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
npm run e2e
# Fix formatting issues
npx prettier --write src/
# Check Polaris audit freshness
kubectl get --raw "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json" | jq -r '.AuditTime'
# Restart Polaris for fresh audit
kubectl rollout restart deployment -n polaris polaris-dashboard
Anti-Patterns (What NOT to Do)
- ❌ Import from
@mui/materialor@mui/icons-material→ breaks plugin - ❌ Use
anytype → strict TypeScript required - ❌ Push code changes directly to main → always use feature branches
- ❌ Grant broader RBAC than
get services/proxy→ security risk - ❌ Use ClusterRole instead of namespaced Role → violates least privilege
- ❌ Forget to run
npx prettier --write src/→ CI will fail - ❌ Use inline styles without CSS variables → breaks dark mode
- ❌ Try to query K8s resources directly → plugin only has service proxy access
- ❌ Import Headlamp
Linkfor plugin routes → use react-router-domLink+Router.createRouteURL() - ❌ Assume Polaris continuously re-audits → it only audits at pod startup
Quick Diagnosis Guide
Symptom: Plugin not in sidebar
→ Check: Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
→ Check: Plugin installed? kubectl get configmap headlamp-plugin-config -n kube-system
Symptom: 403 Access Denied
→ Check: RBAC binding exists? kubectl get role,rolebinding -n polaris
→ Fix: Apply RBAC example from docs/deployment/rbac.md
Symptom: 404 or 503
→ Check: Polaris installed? kubectl get pods -n polaris
→ Check: Service exists? kubectl get svc polaris-dashboard -n polaris
Symptom: Stale audit data
→ Fix: kubectl rollout restart deployment -n polaris polaris-dashboard
→ Verify: Check AuditTime in UI matches current date
Symptom: Settings page empty or broken
→ Check: Plugin version ≥ v0.3.3?
→ Fix: Upgrade plugin and hard refresh browser
Symptom: CI prettier check fails
→ Fix: npx prettier --write src/
→ Commit: Include formatting fixes in your PR
Symptom: Dark mode white backgrounds
→ Check: Plugin version ≥ v0.3.5?
→ Fix: Upgrade and hard refresh browser
Historical Context
Why Service Proxy Instead of ConfigMaps?
Early versions (< v0.0.10) incorrectly documented ConfigMap RBAC. The plugin never accessed ConfigMaps - it always used the service proxy. This was clarified in v0.0.10.
Why No MUI Imports?
v0.3.2 removed direct MUI imports because they caused plugin load failures. Headlamp provides all needed MUI components as re-exports through CommonComponents.
Why React Context?
ADR-001 documents the switch to React Context. Before v0.3.0, each component called usePolarisData() independently, causing duplicate API requests. Context ensures a single shared fetch.
Why No Continuous Polaris Audits?
Polaris dashboard mode runs a one-time audit at pod startup and caches results. This is by design in Polaris itself. For continuous auditing, Polaris would need to be configured in webhook mode (admission controller), which is a different deployment pattern.
Last Updated: 2026-02-12 Version: v0.4.1 Target Headlamp: v0.26+ Target Polaris: v9.x