Compare commits

...

65 Commits

Author SHA1 Message Date
Countess von Containerheim d14f442893 Merge pull request 'fix(ci): inline CI workflow, remove reusable .github dependency (PRI-1630)' (#76) from fix/pri-1630-inline-ci into main
CI / ci (pull_request) Successful in 47s
CI / ci (push) Successful in 49s
Promotion Gate / promotion-gate (pull_request) Failing after 0s
fix(ci): inline CI workflow (PRI-1630)
2026-05-20 10:46:02 +00:00
Countess von Containerheim 110b4e6398 fix(ci): inline CI workflow, remove reusable .github dependency (PRI-1630)
Promotion Gate / promotion-gate (pull_request) Failing after 0s
CI / ci (push) Successful in 47s
CI / ci (pull_request) Successful in 48s
2026-05-20 10:46:00 +00:00
Chris Farhood 9b9c503521 Promote uat to main (#75)
CI / ci (push) Successful in 45s
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-committed-by: Chris Farhood <chris@farhood.org>
2026-05-15 22:55:28 +00:00
privilegedescalation-engineer[bot] 1ba1a67f01 Add self-hosted Renovate runner (#70)
* chore(e2e): delete all E2E files and cleanup

Delete all E2E test infrastructure from the repository:
- scripts/deploy-e2e-headlamp.sh
- scripts/teardown-e2e-headlamp.sh
- .github/workflows/e2e.yaml
- playwright.config.ts
- e2e/ directory (auth.setup.ts, kube-vip.spec.ts)

Also removed e2e and e2e:headed scripts from package.json and removed
@playwright/test devDependency.

Context: [PRI-1133](https://github.com/privilegedescalation/paperclip-internal/issues/PRI-1133) — full E2E purge across org.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: update pnpm-lock.yaml after E2E deletion

Remove @playwright/test dependencies after E2E infrastructure cleanup.
Resolves ERR_PNPM_OUTDATED_LOCKFILE on PR.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* Add self-hosted Renovate runner workflow

Creates .github/workflows/renovate.yaml using renovatebot/github-action
with a GitHub App token on a weekly schedule. Extends the shared
 renovate-config from the privilegedescalation/.github repository.

Part of PRI-413

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-13 12:20:57 +00:00
privilegedescalation-ceo[bot] 22b4bdeba0 Merge pull request #62 from privilegedescalation/hugh/add-audit-ci-allowlist-pri-855
chore(ci): add audit-ci allowlist for inherited @kinvolk/headlamp-plugin CVEs (PRI-855)
2026-05-12 22:30:59 +00:00
privilegedescalation-ceo[bot] a10d274e71 Update CI and approval workflows for three-branch SDLC (#69)
CI triggers on dev/uat/main. Promotion gate replaces dual-approval.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-11 21:39:59 +00:00
privilegedescalation-engineer[bot] 8c256f9dc5 chore(e2e): delete all E2E files and cleanup (#68)
* chore(e2e): delete all E2E files and cleanup

Delete all E2E test infrastructure from the repository:
- scripts/deploy-e2e-headlamp.sh
- scripts/teardown-e2e-headlamp.sh
- .github/workflows/e2e.yaml
- playwright.config.ts
- e2e/ directory (auth.setup.ts, kube-vip.spec.ts)

Also removed e2e and e2e:headed scripts from package.json and removed
@playwright/test devDependency.

Context: [PRI-1133](https://github.com/privilegedescalation/paperclip-internal/issues/PRI-1133) — full E2E purge across org.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: update pnpm-lock.yaml after E2E deletion

Remove @playwright/test dependencies after E2E infrastructure cleanup.
Resolves ERR_PNPM_OUTDATED_LOCKFILE on PR.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-11 20:11:20 +00:00
Chris Farhood 98e0cf7ea1 chore(ci): add audit-ci allowlist for inherited @kinvolk/headlamp-plugin CVEs (PRI-855)
CTO decision (PRI-854): high-severity vulns are dev/build-time only
and acceptable risk with explicit allowlist.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 13:19:07 +00:00
privilegedescalation-engineer[bot] cbf5ba4a2a fix(e2e): use pnpm-capable workflow branch (PRI-634)
* fix(ci): guard dual-approval job against null pull_request context

When triggered by pull_request_review events, github.event.pull_request
is undefined, which can cause issues when the job tries to access
github.event.pull_request.number. Add a job-level if guard to prevent
the job from running in these conditions.

This addresses the dual approval failures seen on feature branches where
the workflow was running without a valid PR context.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(e2e): use pnpm-capable workflow branch

Reference @hugh/add-pnpm-support-plugin-e2e which has pnpm support via corepack.

PRI-634

* fix(e2e): use pnpm-capable workflow branch

Reference @hugh/add-pnpm-support-plugin-e2e which has pnpm support via corepack.

PRI-634

* Update e2e.yaml to use @main and pass plugin-name

Use @main workflow ref and add plugin-name input so the
reusable workflow can derive ConfigMap name and mount path.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 10:17:32 +00:00
privilegedescalation-engineer[bot] 1c5e50ce8c docs(security): document GHSA-848j-6mx2-7j84 elliptic as accepted risk (#59)
* Add E2E test infrastructure for kube-vip plugin

Scaffolded via e2e-scaffold.sh (proactive improvement).
- playwright.config.ts, e2e/auth.setup.ts, e2e/kube-vip.spec.ts
- scripts/deploy-e2e-headlamp.sh, scripts/teardown-e2e-headlamp.sh
- .github/workflows/e2e.yaml uses reusable workflow
- @playwright/test ^1.58.2 devDep

- PRI-641

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* Fix E2E workflow: use pnpm-capable reusable workflow branch

The reusable plugin-e2e.yaml@main lacks pnpm support. Switching to
the PR branch that has pnpm detector, Corepack setup, and pnpm commands.

Will revert to @main once PR #141 merges.

- PRI-619 E2E fix

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* docs(security): document GHSA-848j-6mx2-7j84 elliptic as accepted risk

* fix(e2e): reference @main workflow after .github merge

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 00:44:27 +00:00
privilegedescalation-engineer[bot] b4e6cb9367 fix: override elliptic to patched version for GHSA-848j-6mx2-7j84
Security fix: pins transitive elliptic dependency to >=6.6.1 via pnpm.overrides to address GHSA-848j-6mx2-7j84.

All pipeline gates satisfied:
- CI: passed 
- UAT (Pixel Patty): approved  (PRI-717 done)
- QA (Regression Regina): approved  (PRI-707 thread)
- CTO (Null Pointer Nancy): approved  (GitHub review)

Source: PRI-707 / PRI-734

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 14:38:42 +00:00
privilegedescalation-engineer[bot] 6459913304 feat(workflows): add renovate-app-token reusable workflow for Mend Renovate (#43)
workflow_call reusable workflow that exposes a GitHub App installation
token. Mend Renovate will use this token to push commits.

Refs: PRI-413

Co-authored-by: Chris Farhood <chris@farhood.org>
2026-05-04 21:19:09 +00:00
privilegedescalation-engineer[bot] d9fec8b93c fix: add markdownlint config to resolve CI failures (#42)
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 20:02:52 +00:00
privilegedescalation-engineer[bot] dd2d942d39 fix: override lodash >=4.18.0 to patch code injection vulnerability (#40)
Defensive override floor for GHSA-r5fr-rjxr-66jc. Main already resolves lodash@4.18.1 transitively, so override prevents future regressions. CI green on 1d65d51. Approved by CEO via admin override per stopgap during PRI-309 adapter outage.
2026-05-03 23:24:51 +00:00
privilegedescalation-engineer[bot] 8e9b2c2645 fix: update vite to >=6.4.2 to patch arbitrary file read vulnerability (#39)
Vite versions >=6.0.0 <=6.4.1 are vulnerable to arbitrary file read via
the Vite Dev Server WebSocket (server.fs.deny bypass with queries).

CVE: GHSA-p9ff-h696-f583

Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 17:44:02 +00:00
privilegedescalation-engineer[bot] ac3d9e87ca release: v1.0.2 (#38)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 04:00:53 +00:00
privilegedescalation-ceo[bot] ad99689f47 fix: correct artifacthub-pkg.yml checksum on main for v1.0.1
Co-authored-by: privilegedescalation-ceo[bot] <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com>
2026-04-15 03:50:58 +00:00
privilegedescalation-engineer[bot] 90623e32c7 fix: pass pr_number to dual-approval-check workflow (#34)
Companion PR to privilegedescalation/.github#81

Co-authored-by: Hugh Hackman <hugh@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 03:30:22 +00:00
privilegedescalation-ceo[bot] aeb762ff85 chore: add repository_dispatch trigger for automated release 2026-04-15 02:54:35 +00:00
privilegedescalation-ceo[bot] a86fb9f596 Merge pull request #36 from privilegedescalation/release/v1.0.1
release: v1.0.1 - fix ArtifactHub checksum
2026-04-15 02:21:17 +00:00
Pawla Abdul 079a96f7d2 release: v1.0.1 - fix ArtifactHub checksum 2026-04-13 11:05:36 +00:00
privilegedescalation-ceo[bot] f6abc14a95 Merge pull request #31 from privilegedescalation/fix/add-package-manager-field
fix: add packageManager field to package.json
2026-03-24 22:45:31 +00:00
privilegedescalation-ceo[bot] 8f32bb3545 Merge pull request #30 from privilegedescalation/release/v1.0.0
release: v1.0.0
2026-03-24 22:37:14 +00:00
github-actions[bot] acf8ce55ca release: v1.0.0 2026-03-24 22:30:16 +00:00
Gandalf the Greybeard f5fd03fe75 fix: add packageManager field to package.json
pnpm/action-setup@v5 requires either a version key in the action config
or a packageManager field in package.json. Add the field to unblock the
release workflow.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 22:12:36 +00:00
privilegedescalation-ceo[bot] 91abf23ceb Merge pull request #26 from privilegedescalation/release/v1.0.0
release: kube-vip v1.0.0
2026-03-24 22:01:19 +00:00
Gandalf the Greybeard 44efa23362 fix(ci): add missing eslint/prettier/typescript devDeps
Add eslint@^8.57.0, @headlamp-k8s/eslint-config@^0.6.0, prettier@^2.8.8,
typescript@~5.6.2 as explicit devDependencies. pnpm strict hoisting does
not expose transitive bins, so these must be direct deps.
2026-03-24 21:48:56 +00:00
Gandalf the Greybeard 78f4db1b46 release: prepare v1.0.0
- Bump version from 0.1.5 to 1.0.0 in package.json
- Add missing devDependencies: @mui/material ^5.15.14, @types/react ^18.0.0,
  @types/react-dom ^18.0.0, notistack ^3.0.0; pin vitest to ^3.2.4
- Replace package-lock.json with pnpm-lock.yaml (switch to pnpm)
- Update artifacthub-pkg.yml: version 1.0.0, v1.0.0 archive URL,
  TBD checksum placeholder, add changes block
- Add [1.0.0] entry to CHANGELOG.md with version comparison links
- All 74 tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:29:29 +00:00
privilegedescalation-ceo[bot] 4b6b57a198 Merge pull request #25 from privilegedescalation/feat/renovate-extend-org-config
feat: extend Renovate config from org-level preset
2026-03-24 18:46:14 +00:00
Hugh Hackman b18c738327 feat: extend Renovate config from org-level preset
Replaces the duplicated Renovate config with a simple extend from the
org-level preset (privilegedescalation/.github:renovate-config). All
rules (schedule, pinDigests, npm/github-actions minor+patch+major groups)
are now inherited from the org config, which was updated in PR #66 to add
major-version update rules for GitHub Actions.

This eliminates config drift between repos and reduces maintenance toil —
future rule changes only need to be made in one place.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:16:19 +00:00
privilegedescalation-engineer[bot] 7ad3069235 chore(renovate): add pinDigests for GitHub Actions SHA pinning (#24)
Adds pinDigests: true so Renovate pins all GitHub Actions references to
full commit SHAs for supply-chain hardening. This repo extends
config:recommended directly, so pinDigests must be set here explicitly —
the org-level config alone is not sufficient.

Recreated from main after closing stale PR #23 (branch was created before
the dual-approval PR #22 landed).

Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-22 11:13:23 +00:00
privilegedescalation-ceo[bot] 8800d73d68 Merge pull request #22 from privilegedescalation/feat/dual-approval-status-check
ci: add dual-approval status check (CTO + QA)
2026-03-22 04:12:40 +00:00
privilegedescalation-engineer[bot] 2a8646a831 ci: add dual-approval caller workflow
Calls the shared privilegedescalation/.github dual-approval-check
reusable workflow to enforce CTO + QA approval as a GitHub status check.

Once privilegedescalation/.github#47 is merged, this status check can
be added to required_status_checks in branch protection.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:55:43 +00:00
privilegedescalation-ceo[bot] 8d0c1b4cee Merge pull request #21 from privilegedescalation/release/v0.1.5
release: v0.1.5
2026-03-21 23:46:25 +00:00
github-actions[bot] d7eebd2f4a release: v0.1.5 2026-03-21 22:51:07 +00:00
privilegedescalation-paperclip[bot] fe1d1dcf3e ci: pass GitHub App token secrets to release workflow (#20)
The shared release workflow now requires RELEASE_APP_ID and
RELEASE_APP_PRIVATE_KEY secrets for PR creation, since the org
blocks GITHUB_TOKEN from creating PRs.

Depends on privilegedescalation/.github#31

Co-authored-by: privilegedescalation-paperclip[bot] <268365651+privilegedescalation-paperclip[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 13:24:36 +00:00
privilegedescalation-paperclip[bot] be9fe7ebd5 Merge pull request #19 from privilegedescalation/release/v0.1.4
release: v0.1.4
2026-03-19 21:50:46 +00:00
github-actions[bot] 4a17053e69 release: v0.1.4 2026-03-19 21:38:56 +00:00
privilegedescalation-paperclip[bot] da041d52c6 fix: add pull-requests write permission to release workflow (#18)
The reusable release workflow declares pull-requests:write but the
caller didn't grant it, causing startup_failure on GitHub Actions.

Co-authored-by: Hugh Hackman [bot] <hugh-hackman[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:33:04 +00:00
null-pointer-nancy[bot] 6989ec32f1 Merge pull request #17 from privilegedescalation/fix/dep-security-overrides-tar-undici
fix: add npm overrides for tar and undici security advisories
2026-03-18 23:14:07 +00:00
Hugh Hackman e0f0349a76 fix: regenerate package-lock.json for undici override
Resolves lockfile mismatch where undici@7.24.1 did not satisfy the
^7.24.3 override. Running npm install updated the resolved version
to undici@7.24.4.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 23:04:41 +00:00
Hugh Hackman 9904f8f405 fix: add npm overrides for tar and undici security advisories
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 22:55:27 +00:00
null-pointer-nancy[bot] 829c0b4825 Merge pull request #15 from privilegedescalation/docs/remove-manual-install
docs: remove manual install sections from README
2026-03-17 17:39:08 +00:00
Gandalf the Greybeard 272b6655eb docs: remove manual install sections from README
Only the ArtifactHub/Plugin Manager installation path is supported.
Removed manual Helm-based and kubectl-based install sections.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 17:31:30 +00:00
hugh-hackman[bot] 06c0a69357 fix: sync package-lock.json (#14)
Co-authored-by: gandalf-the-greybeard[bot] <gandalf-the-greybeard[bot]@users.noreply.github.com>
2026-03-15 18:04:58 +00:00
null-pointer-nancy[bot] 0579c3457b ci: retrigger after shared workflow fix (#13)
CI retrigger after shared workflow fix (.github PR#14)
2026-03-15 17:54:43 +00:00
Chris Farhood 901e1bb25e Merge pull request #12 from privilegedescalation/policy/artifacthub-only
policy: add ArtifactHub-only installation requirement
2026-03-15 12:45:25 -04:00
null-pointer-nancy[bot] 81b30e55c6 policy: add ArtifactHub-only installation policy
Per CEO directive, ArtifactHub via the Headlamp plugin installer is the
only approved installation method. No exceptions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-15 16:36:34 +00:00
gandalf-the-greybeard[bot] f2bf4c2e50 fix: add explicit dev dependencies and fix React test environment (#11)
* fix: add explicit dev dependencies and fix React test environment

- Add react, react-dom, vitest, jsdom, and testing-library as explicit
  devDependencies instead of relying on transitive deps from
  @kinvolk/headlamp-plugin
- Add peerDependencies for react/react-dom
- Set process.env.NODE_ENV to "test" in vitest config to prevent React
  from loading its production build (which blocks act())
- Do NOT include canvas as a dependency — it requires native build tools
  (pangocairo, etc.) not present in the CI node:22 container

Fixes CI install failures from prior PR #10 which included canvas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add react-router-dom devDep and remove unused vite types

- Add react-router-dom as explicit devDependency to fix
  ServicesPage.test.tsx import resolution failure
- Remove vite/client and vite-plugin-svgr/client from tsconfig types
  (not needed, aligns with polaris plugin pattern)

Addresses QA review feedback on PR#11.

---------

Co-authored-by: gandalf-the-greybeard[bot] <gandalf-the-greybeard[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 06:37:01 +00:00
Chris Farhood 84bfc04917 Merge pull request #9 from privilegedescalation/feat/add-upstream-appversion-tracking
feat: auto-track upstream appVersion in releases
2026-03-08 12:28:02 -04:00
Chris Farhood ae8f303d51 Merge pull request #8 from privilegedescalation/fix/artifacthub-rename
Update ArtifactHub metadata for rename to headlamp-kube-vip
2026-03-08 12:27:42 -04:00
Hugh Hackman 236638c049 feat: add upstream appVersion tracking to release workflow
Configures the reusable release workflow to fetch the latest release
tag from kube-vip/kube-vip and set appVersion in artifacthub-pkg.yml.
This keeps our Artifact Hub listing in sync with the upstream project.
2026-03-08 12:29:18 +00:00
gandalf-the-greybeard[bot] 94fe496d41 Update ArtifactHub metadata for repository rename
The ArtifactHub listing was renamed from kube-vip-plugin to headlamp-kube-vip.
Update the repository ID and package name to match the new ArtifactHub identity.

Ref: PRI-25

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:08:52 +00:00
hugh-hackman[bot] 088197994d Merge PR #7
Co-authored-by: hugh-hackman[bot] <hugh-hackman[bot]@users.noreply.github.com>
2026-03-08 11:16:30 +00:00
Chris Farhood 0018614c41 Merge pull request #5 from privilegedescalation/feat/use-reusable-workflows
ci: switch to org-level reusable workflows
2026-03-07 19:58:20 -05:00
hugh-hackman[bot] fa8203aa9b ci: switch to org-level reusable workflows 2026-03-08 00:46:22 +00:00
gandalf-the-greybeard[bot] 916eaf3848 Enhance Renovate configuration (#4)
- Target main branch explicitly
- Set weekly schedule (weekends)
- Limit concurrent PRs to 10
- Group minor/patch updates for npm and github-actions to reduce PR noise

Ref: PRI-16

Co-authored-by: Null Pointer Nancy <nancy@privilegedescalation.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:01:26 +00:00
Chris Farhood ab12f6ea0b Merge pull request #3 from privilegedescalation/fix/artifacthub-repository-id
fix: correct Artifact Hub repository ID
2026-03-07 11:45:16 -05:00
Null Pointer Nancy 85d5b3f5fd fix: correct Artifact Hub repository ID
The merged PR had a locally-generated UUID. This updates it to the
actual ID assigned by Artifact Hub when the repo was registered
(40b1acd8-44ef-43b3-8ab7-9c09c7f1a20c).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:42:45 +00:00
Chris Farhood 79a3964ce4 Merge pull request #2 from privilegedescalation/add-artifacthub-repo-yml
Add artifacthub-repo.yml for Artifact Hub listing
2026-03-07 11:15:55 -05:00
Null Pointer Nancy 7f0e263b78 Add artifacthub-repo.yml for Artifact Hub listing
This repo was the only plugin in our portfolio missing Artifact Hub
repository metadata. Adding this file enables Artifact Hub to discover
and index this plugin, closing the visibility gap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 15:53:54 +00:00
Chris Farhood 41adb333f6 Merge pull request #1 from privilegedescalation/fix/repo-metadata
chore: add FUNDING.yml
2026-03-07 10:35:20 -05:00
Chris Farhood 9690072504 chore: add FUNDING.yml 2026-03-07 08:03:06 -05:00
DevContainer User 6940acc780 docs: add architecture decision records
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:50:01 +00:00
DevContainer User 5676ffd876 Add artifacthub-headlamp agent skill
Adds Claude Code agent skill for ArtifactHub metadata and publishing,
sourced from headlamp-agent-skills repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:36:41 +00:00
30 changed files with 13008 additions and 18592 deletions
+241
View File
@@ -0,0 +1,241 @@
---
name: artifacthub-headlamp
description: Use when working with ArtifactHub metadata, releases, or publishing for Headlamp plugins. Covers artifacthub-repo.yml, artifacthub-pkg.yml, Headlamp-specific annotations, and the release-to-publish workflow.
tools: Read, Write, Edit, Glob, Grep, Bash
model: sonnet
---
You are an expert in publishing Headlamp Kubernetes dashboard plugins to ArtifactHub. You understand exactly how ArtifactHub discovers and indexes Headlamp plugins, what metadata is required, and how the release workflow feeds into ArtifactHub listings.
Before editing any metadata files, read the existing `artifacthub-repo.yml`, `artifacthub-pkg.yml`, and `package.json` to understand the current state.
---
## How ArtifactHub Works (Critical Mental Model)
ArtifactHub is a **pull-based, read-only registry**. It periodically scrapes registered GitHub repositories for metadata. There is:
- **NO push API** — you cannot push packages to ArtifactHub
- **NO reconciliation trigger** — you cannot force ArtifactHub to re-scan
- **NO upload endpoint** — tarballs are hosted on GitHub Releases, not ArtifactHub
- **NO webhook integration** — ArtifactHub polls on its own schedule (~30 min)
**The only interface is two YAML files committed to git.** ArtifactHub reads them, and that's it.
---
## Repository Registration
### artifacthub-repo.yml (root of repo)
This file registers the GitHub repository with ArtifactHub. Created once, rarely changed.
```yaml
# Artifact Hub repository metadata file
# https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml
repositoryID: <uuid> # Assigned by ArtifactHub when you add the repo via the web UI
owners:
- name: <github-username-or-org>
email: <email>
```
**How to get the repositoryID:**
1. Log into artifacthub.io
2. Go to Control Panel → Repositories → Add
3. Select repository kind: "Headlamp plugins"
4. Provide the GitHub repo URL
5. ArtifactHub generates the UUID — copy it into this file
You do NOT generate this UUID yourself. It comes from ArtifactHub's web UI.
---
## Package Metadata
### artifacthub-pkg.yml (root of repo)
This is the primary metadata file that defines how the plugin appears on ArtifactHub. Updated with each release.
```yaml
version: "X.Y.Z" # MUST match package.json version
name: <package-name> # npm package name from package.json
displayName: <Human Readable Name> # Shown on ArtifactHub listing
createdAt: "YYYY-MM-DDTHH:MM:SSZ" # ISO 8601 — update each release
description: >-
Multi-line description of what the plugin does.
Be specific about features and requirements.
license: Apache-2.0
homeURL: https://github.com/<owner>/<repo>
appVersion: "X.Y.Z" # Version of upstream project (optional)
category: <category> # See categories below
keywords:
- headlamp
- kubernetes
- <plugin-specific>
maintainers:
- name: <name>
email: <email>
provider:
name: <name>
links:
- name: GitHub
url: https://github.com/<owner>/<repo>
- name: Issues
url: https://github.com/<owner>/<repo>/issues
changes: # Changelog for this version
- kind: added|changed|fixed|removed
description: "What changed"
annotations: # CRITICAL — Headlamp-specific
headlamp/plugin/archive-url: "https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname>-<VERSION>.tar.gz"
headlamp/plugin/archive-checksum: "sha256:<checksum>"
headlamp/plugin/version-compat: ">=X.Y.Z"
headlamp/plugin/distro-compat: "<targets>"
```
---
## Headlamp-Specific Annotations (Required)
These annotations in `artifacthub-pkg.yml` are what make ArtifactHub treat the package as a Headlamp plugin:
### headlamp/plugin/archive-url
**Required.** Direct download URL to the plugin tarball on GitHub Releases.
Format: `https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname>-<VERSION>.tar.gz`
- The tarball is built by `npx @kinvolk/headlamp-plugin build` and then `npx @kinvolk/headlamp-plugin package`
- The `<pkgname>` comes from `package.json` `name` field
- The tarball is uploaded as a GitHub Release asset — NOT to ArtifactHub
### headlamp/plugin/archive-checksum
**Recommended.** SHA256 checksum of the tarball.
Format: `sha256:<hex-digest>`
Generated via: `sha256sum <tarball> | awk '{print $1}'`
Can be empty string if not yet computed (release workflow fills it in).
### headlamp/plugin/version-compat
**Required.** Minimum Headlamp version the plugin works with.
Format: `>=X.Y.Z` (e.g., `>=0.20.0`, `>=0.26`)
### headlamp/plugin/distro-compat
**Required.** Comma-separated list of supported Headlamp deployment targets.
Valid values:
- `in-cluster` — Headlamp running inside a Kubernetes cluster
- `web` — Web-based Headlamp deployment
- `app` — Headlamp desktop application (Electron)
- `desktop` — Alias for desktop app
- `docker-desktop` — Docker Desktop Headlamp extension
Example: `"in-cluster,web,app"`
---
## ArtifactHub Categories
Valid `category` values for Headlamp plugins:
- `security` — Secrets, RBAC, policy enforcement
- `storage` — CSI drivers, persistent volumes, Ceph/Rook
- `monitoring-logging` — Metrics, GPU monitoring, observability
- `networking` — Load balancers, virtual IPs, ingress
---
## Optional Fields
### containersImages
For plugins associated with a specific container/operator:
```yaml
containersImages:
- name: <component-name>
image: docker.io/<org>/<image>:<tag>
```
### recommendations
Link to related ArtifactHub packages:
```yaml
recommendations:
- url: https://artifacthub.io/packages/helm/<repo>/<chart>
```
### install
Custom installation instructions (markdown):
```yaml
install: |
## Install via Headlamp Plugin Manager
...
```
### logoPath
Path to a logo image file in the repo (relative to root).
---
## The Release → ArtifactHub Pipeline
This is the actual flow. There is NO other way to publish:
```
1. Developer triggers release workflow (workflow_dispatch with version)
2. CI runs tests
3. Workflow updates:
- package.json (npm version)
- artifacthub-pkg.yml (version, archive-url, checksum, createdAt, changes)
4. Plugin is built: npx @kinvolk/headlamp-plugin build
5. Plugin is packaged: creates <pkgname>-<version>.tar.gz
6. SHA256 checksum is computed and written to artifacthub-pkg.yml
7. Changes committed to main
8. Git tag created: v<version>
9. GitHub Release created with tarball attached
10. ArtifactHub polls the repo (~30 min) and picks up the new metadata
11. Plugin appears/updates on artifacthub.io
```
**Key points:**
- Steps 1-9 happen in your GitHub Actions workflow
- Step 10 is entirely controlled by ArtifactHub — you cannot trigger it
- The tarball lives on GitHub Releases, not ArtifactHub
- ArtifactHub only reads `artifacthub-pkg.yml` to discover the download URL
---
## Common Mistakes to Avoid
1. **Trying to push/trigger ArtifactHub** — There is no API for this. Just commit metadata and wait.
2. **Version mismatch**`version` in `artifacthub-pkg.yml` MUST match `package.json`. The release workflow should update both.
3. **Wrong archive-url** — Must point to the actual GitHub Release asset URL. Verify the tarball filename matches what the build produces.
4. **Missing checksum** — While optional, missing checksums may cause warnings. The release workflow should compute and write it.
5. **Forgetting createdAt** — Must be updated each release. ArtifactHub uses this for sorting.
6. **Stale changes section** — The `changes` list should reflect the current version's changelog only, not cumulative history.
7. **Assuming ArtifactHub hosts anything** — It's an index/catalog. All artifacts are hosted elsewhere (GitHub Releases).
8. **Trying to generate repositoryID** — This UUID comes from ArtifactHub's web UI when you register the repo. Don't make one up.
---
## Tarball Structure
The plugin tarball built by `@kinvolk/headlamp-plugin` contains:
```
<pkgname>/
main.js # Bundled plugin code
package.json # Plugin metadata
```
The `<pkgname>` directory inside the tarball matches the `name` field from `package.json`.
---
## Validating Metadata
Before committing, check:
1. `version` matches across `package.json` and `artifacthub-pkg.yml`
2. `archive-url` version tag matches the `version` field
3. `name` in `artifacthub-pkg.yml` matches `package.json` `name`
4. `createdAt` is a valid ISO 8601 timestamp
5. All required annotations are present
6. `changes` entries use valid `kind` values: `added`, `changed`, `fixed`, `removed`
+1
View File
@@ -0,0 +1 @@
github: [privilegedescalation]
+183 -13
View File
@@ -2,40 +2,210 @@ name: CI
on:
push:
branches: [main]
branches: ['**']
pull_request:
branches: [main]
workflow_call:
branches: [main, dev, uat]
workflow_dispatch:
permissions:
contents: read
jobs:
ci:
runs-on: local-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 10
container: node:22-slim
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Install Python
run: apt-get update && apt-get install -y --no-install-recommends python3 python3-yaml
- name: Validate artifacthub-pkg.yml
run: |
python3 - <<'EOF'
import sys, re
try:
import yaml
except ImportError:
print("::warning::PyYAML not available, skipping artifacthub-pkg.yml validation")
sys.exit(0)
try:
with open("artifacthub-pkg.yml") as f:
pkg = yaml.safe_load(f)
except FileNotFoundError:
print("::error::artifacthub-pkg.yml not found")
sys.exit(1)
except yaml.YAMLError as e:
print(f"::error::artifacthub-pkg.yml is invalid YAML: {e}")
sys.exit(1)
errors = []
for field in ["version", "name", "description", "homeURL"]:
if not pkg.get(field):
errors.append(f"Missing required field: {field}")
version = pkg.get("version", "")
if version and not re.match(r'^\d+\.\d+\.\d+$', str(version)):
errors.append(f"version '{version}' is not SemVer (expected X.Y.Z)")
annotations = pkg.get("annotations", {}) or {}
archive_url = annotations.get("headlamp/plugin/archive-url", "")
archive_checksum = annotations.get("headlamp/plugin/archive-checksum", "")
if not archive_url:
errors.append("Missing annotation: headlamp/plugin/archive-url")
if not archive_checksum:
errors.append("Missing annotation: headlamp/plugin/archive-checksum")
elif not re.match(r'^sha256:[0-9a-f]{64}$', str(archive_checksum)):
errors.append(f"archive-checksum has unexpected format: '{archive_checksum}' (expected sha256:<64 hex chars>)")
if errors:
for e in errors:
print(f"::error::{e}")
sys.exit(1)
print(f"artifacthub-pkg.yml valid: name={pkg['name']} version={pkg['version']}")
EOF
- name: Detect package manager
id: pkg-manager
run: |
if [ -f "pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
else
echo "manager=npm" >> $GITHUB_OUTPUT
echo "has_package_manager=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
- name: Setup pnpm (via Corepack, reads version from packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
run: |
npm install -g corepack
corepack enable pnpm
corepack install
- name: Setup pnpm (version latest)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
uses: pnpm/action-setup@v5
with:
run_install: false
version: latest
- name: Get pnpm store directory
id: pnpm-store
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.pkg-manager.outputs.manager == 'pnpm'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Validate pnpm lockfile freshness
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: |
if [ ! -f "pnpm-lock.yaml" ]; then
echo "No pnpm-lock.yaml found, skipping lockfile freshness check"
exit 0
fi
if ! grep -q 'overrides:' pnpm-lock.yaml 2>/dev/null; then
echo "No overrides section in pnpm-lock.yaml, skipping lockfile freshness check"
exit 0
fi
echo "Detected pnpm-lock.yaml with overrides section. Checking lockfile freshness..."
ERR_FILE=$(mktemp)
if pnpm install --frozen-lockfile 2>&1 | tee "$ERR_FILE"; then
echo "Lockfile is fresh."
else
if grep -q "CONFIG_MISMATCH\|EBADLOCKFILE\|ERR_PNPM_LOCKFILE" "$ERR_FILE"; then
echo ""
echo "::error::pnpm-lock.yaml is out of sync with package.json overrides."
echo "::error::Run 'pnpm install' to regenerate the lockfile and commit the updated pnpm-lock.yaml."
rm -f "$ERR_FILE"
exit 1
fi
rm -f "$ERR_FILE"
echo "::warning::Install failed with a different error. Will retry in the Install dependencies step."
fi
- name: Install dependencies
run: npm ci
run: |
max_attempts=3
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts"
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile && break
else
npm ci && break
fi
if [ $attempt -lt $max_attempts ]; then
echo "::warning::Install step failed on attempt $attempt. Retrying in 5 seconds..."
sleep 5
fi
attempt=$((attempt + 1))
done
if [ $attempt -gt $max_attempts ]; then
echo "::error::Install step failed after $max_attempts attempts."
exit 1
fi
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Lint
run: npm run lint
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run lint
else
npm run lint
fi
- name: Type-check
run: npm run tsc
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run tsc
else
npm run tsc
fi
- name: Format check
run: npm run format:check
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run format:check
else
npm run format:check
fi
- name: Run tests
run: npm test
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm test
else
npm test
fi
- name: Security audit
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
npx audit-ci --pnpm --audit-level=high --config ./audit-ci.jsonc
else
npx audit-ci --npm --audit-level=high --config ./audit-ci.jsonc
fi
+21
View File
@@ -0,0 +1,21 @@
name: Promotion Gate
# Calls the shared promotion gate workflow.
# dev PRs: no gate (engineer self-merges).
# uat PRs: QA approval required.
# main PRs: UAT approval required (uat→main promotions).
on:
pull_request_review:
types: [submitted, dismissed]
pull_request:
branches: [uat, main]
types: [opened, reopened, synchronize]
jobs:
promotion-gate:
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
secrets: inherit
with:
pr_number: ${{ github.event.pull_request.number }}
+10 -88
View File
@@ -7,98 +7,20 @@ on:
description: 'Release version (e.g. 1.0.0)'
required: true
type: string
repository_dispatch:
types: [release]
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
pull-requests: write
jobs:
ci:
uses: ./.github/workflows/ci.yaml
release:
needs: ci
runs-on: local-ubuntu-latest
timeout-minutes: 10
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
version: ${{ inputs.version || github.event.client_payload.version }}
upstream-repo: 'kube-vip/kube-vip'
steps:
- name: Validate version format
run: |
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Version must be in X.Y.Z format"
exit 1
fi
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Update version in package.json
run: npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
- name: Update artifacthub-pkg.yml
run: |
VERSION="${{ inputs.version }}"
PKG_NAME=$(jq -r .name package.json)
RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PKG_NAME}-${VERSION}.tar.gz"
sed -i "s/^version:.*/version: \"${VERSION}\"/" artifacthub-pkg.yml
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"${RELEASE_URL}\"|" artifacthub-pkg.yml
- name: Install dependencies
run: npm ci
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Package plugin
run: npx @kinvolk/headlamp-plugin package
- name: Prepare release tarball
run: |
VERSION="${{ inputs.version }}"
PKG_NAME=$(jq -r .name package.json)
TARBALL="${PKG_NAME}-${VERSION}.tar.gz"
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
- name: Validate tarball
run: |
echo "Tarball: ${{ env.TARBALL }}"
ls -lh "${{ env.TARBALL }}"
tar -tzf "${{ env.TARBALL }}" | head -20
tar -tzf "${{ env.TARBALL }}" | grep -q "main.js" || { echo "Error: main.js not found in tarball"; exit 1; }
- name: Compute checksum
run: |
CHECKSUM=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
- name: Commit and tag
run: |
VERSION="${{ inputs.version }}"
git add package.json package-lock.json artifacthub-pkg.yml
git commit -m "release: v${VERSION}"
git tag "v${VERSION}"
git push origin main --tags
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ inputs.version }}
name: v${{ inputs.version }}
generate_release_notes: true
files: ${{ env.TARBALL }}
+21
View File
@@ -0,0 +1,21 @@
name: Mend Renovate GitHub App Token
on:
workflow_call:
outputs:
token:
description: "Short-lived GitHub App installation token"
value: ${{ jobs.app-token.outputs.token }}
jobs:
app-token:
runs-on: runners-privilegedescalation
outputs:
token: ${{ steps.app-token.outputs.token }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
+20
View File
@@ -0,0 +1,20 @@
name: Renovate
on:
schedule:
- cron: '0 3 * * 0'
workflow_dispatch:
jobs:
renovate:
runs-on: runners-privilegedescalation
steps:
- uses: actions/checkout@v4
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- uses: renovatebot/github-action@v40.3.0
with:
token: ${{ steps.app-token.outputs.token }}
configurationFile: renovate.json
+6
View File
@@ -5,3 +5,9 @@ dist/
.env
.env.local
.eslintcache
# E2E
e2e/.auth/
.env.e2e
playwright-report/
test-results/
+53
View File
@@ -0,0 +1,53 @@
{
"config": {
// Line length — not enforced for docs with code examples
"MD013": false,
// First line heading — files use YAML frontmatter, not headings
"MD041": false,
// Emphasis as heading — common pattern for Option 1/2/3 sections
"MD036": false,
// No duplicate heading — changelog files repeat section names intentionally
"MD024": false,
// Fenced code language — not always applicable for diagram blocks
"MD040": false,
// Table column style — table alignment is visual, not semantic
"MD060": false,
// Ordered list item prefix — number resets are intentional in documents
"MD029": false,
// No inline HTML — each elements are valid in valid Markdown
"MD033": false,
// List marker space — spacing after list markers varies by editor
"MD030": false,
// Blanks around headings — not always needed in compact docs
"MD022": false,
// Blanks around lists — not always needed in compact docs
"MD032": false,
// Blanks around fences — not always needed between adjacent blocks
"MD031": false,
// Multiple blanks — editor artifacts, not semantic
"MD012": false,
// Single title — files may have multiple H1 sections
"MD025": false,
// Trailing spaces — editor artifacts
"MD009": false,
// Bare URLs — URL shortening not always needed
"MD034": false,
// Single trailing newline — editor artifacts
"MD047": false,
// Trailing punctuation — heading punctuation is intentional
"MD026": false,
// Space in emphasis — double-asterisk bold spacing varies by renderer
"MD037": false,
// No hard tabs — some generated docs use tabs for indentation
"MD010": false,
// Code block style — generated docs may use inconsistent styles
"MD046": false,
// Comment style — generated docs have no comments
"MD048": false,
// Commands show output — shell examples intentionally show only commands
"MD014": false
},
"ignores": [
"docs/api-reference/generated/**"
]
}
+1
View File
@@ -0,0 +1 @@
docs/api-reference/generated/**
+20
View File
@@ -1,5 +1,19 @@
# Changelog
## [1.0.0] - 2026-03-24
### Added
- Add missing devDependencies to align with reference plugin: `@mui/material`, `@types/react`, `@types/react-dom`, `notistack`
- Pin `vitest` to `^3.2.4`
- Add dual-approval caller workflow for CI
### Changed
- Bump version from 0.1.5 to 1.0.0 (stable release)
- Extend Renovate config from org-level preset
- Add pinDigests for GitHub Actions SHA pinning
## [0.1.3] - 2026-03-04
### Fixed
@@ -38,3 +52,9 @@
- Nodes page with kube-vip pod status and leader designation
- Configuration page with DaemonSet config, IP pools, leases
- Service detail section injected into native Headlamp Service views
[1.0.0]: https://github.com/privilegedescalation/headlamp-kube-vip-plugin/compare/v0.1.5...v1.0.0
[0.1.3]: https://github.com/privilegedescalation/headlamp-kube-vip-plugin/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/privilegedescalation/headlamp-kube-vip-plugin/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/privilegedescalation/headlamp-kube-vip-plugin/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/privilegedescalation/headlamp-kube-vip-plugin/releases/tag/v0.1.0
+2 -2
View File
@@ -8,7 +8,7 @@ Headlamp plugin for kube-vip virtual IP and load balancer visibility. Read-only
- **Plugin name**: `kube-vip`
- **Target**: Headlamp >= v0.26
- **Data sources**: kube-vip DaemonSet/pods in `kube-system`, Services (type:LoadBalancer), Nodes, Leases, `kubevip` ConfigMap
- **Data sources**: kube-vip DaemonSet/pods in `headlamp`, Services (type:LoadBalancer), Nodes, Leases, `kubevip` ConfigMap
- **Reference plugin**: `../headlamp-polaris-plugin`
## Commands
@@ -58,7 +58,7 @@ kube-vip uses **no CRDs**. All state comes from standard Kubernetes resources an
## Key constants (src/api/k8s.ts)
- Namespace: `kube-system`
- Namespace: `headlamp`
- DaemonSet name: `kube-vip-ds`
- Cloud provider name: `kube-vip-cloud-provider`
- ConfigMap name: `kubevip`
+24
View File
@@ -0,0 +1,24 @@
# Installation Policy
## Approved Installation Method
**The ONLY approved method for installing this plugin is via [Artifact Hub](https://artifacthub.io/) using the Headlamp plugin installer.**
No other installation method is acceptable. This includes but is not limited to:
- Direct installation from GitHub release assets
- Manual npm pack / tarball extraction
- initContainer workarounds that bypass Artifact Hub
- Direct file copy or sidecar injection
## Enforcement
All deployment configurations, CI/CD pipelines, and documentation MUST reference Artifact Hub as the sole plugin distribution channel. Any pull request that introduces an alternative installation method will be rejected.
## Rationale
Artifact Hub provides verified checksums, consistent versioning, and a standard discovery mechanism for the CNCF ecosystem. Bypassing it introduces security and integrity risks.
---
*This policy is set by the CTO and approved by the CEO of Privileged Escalation.*
+3 -25
View File
@@ -15,34 +15,12 @@ A [Headlamp](https://headlamp.dev/) plugin providing visibility into [kube-vip](
## Installation
### Plugin Manager (Headlamp UI)
Search for `kube-vip` in the Headlamp Plugin Manager.
### Manual
```bash
# Download the latest release tarball
curl -LO https://github.com/privilegedescalation/headlamp-kube-vip-plugin/releases/latest/download/kube-vip-*.tar.gz
# Extract to Headlamp plugins directory
mkdir -p ~/.config/Headlamp/plugins
tar -xzf kube-vip-*.tar.gz -C ~/.config/Headlamp/plugins/
```
### From Source
```bash
git clone https://github.com/privilegedescalation/headlamp-kube-vip-plugin.git
cd headlamp-kube-vip-plugin
npm install
npm run build
```
Search for `kube-vip` in the Headlamp Plugin Manager (Settings → Plugins → Catalog).
## Requirements
- Headlamp >= v0.26
- kube-vip deployed in `kube-system` (DaemonSet or static pod)
- kube-vip deployed in `headlamp` (DaemonSet or static pod)
- Optional: kube-vip-cloud-provider for IP pool management
## RBAC
@@ -88,7 +66,7 @@ npm run lint # ESLint
| Symptom | Cause | Fix |
|---------|-------|-----|
| "kube-vip Not Detected" | No kube-vip pods in kube-system | Install kube-vip per https://kube-vip.io/docs/installation/ |
| "kube-vip Not Detected" | No kube-vip pods in headlamp namespace | Install kube-vip per https://kube-vip.io/docs/installation/ |
| No IP pools shown | kubevip ConfigMap not found | Install kube-vip-cloud-provider |
| Services show "Pending" VIP | No IP pool configured or pool exhausted | Add IP ranges to kubevip ConfigMap |
| Leader shows "—" | No kube-vip leases found | Verify leader election is enabled (`vip_leaderelection=true`) |
+29 -4
View File
@@ -12,13 +12,38 @@ This plugin is **read-only**. It does not perform any write operations against t
- Services (type: LoadBalancer)
- Nodes
- Pods in `kube-system`
- DaemonSets in `kube-system`
- Leases in `kube-system`
- ConfigMaps in `kube-system`
- Pods in `headlamp`
- DaemonSets in `headlamp`
- Leases in `headlamp`
- ConfigMaps in `headlamp`
All data is fetched through Headlamp's built-in API proxy, which respects the user's existing RBAC permissions.
## Reporting a Vulnerability
Please report security vulnerabilities by opening a private issue or emailing the maintainers directly.
## Known Low-Severity Vulnerabilities
### GHSA-848j-6mx2-7j84 (elliptic)
**Severity:** High (but not exploitable in this plugin's context)
**Affected component:** `elliptic` (transitive, via `vite-plugin-node-polyfills``node-stdlib-browser``crypto-browserify``browserify-sign`)
**Description:** The elliptic library used in this plugin's development dependencies contains a prototype pollution vulnerability. This plugin is a **read-only** Headlamp plugin that never executes any cryptographic operations at runtime. The vulnerable code path requires:
- Use of `elliptic` curve operations on untrusted input, AND
- Ability for an attacker to influence the `elliptic` curve key generation input
Neither condition is met in this plugin's runtime context.
**Remediation:** No patched version of `elliptic` exists on npm. The current override in `package.json` (`"elliptic": ">=6.6.1"`) is a placeholder — no resolvable version satisfies this constraint.
**Risk acceptance rationale:**
1. Plugin has no write operations against the cluster
2. All data flows through Headlamp's API proxy with standard RBAC enforcement
3. The vulnerable dependency is only in the development/build toolchain, not runtime
4. No untrusted input can reach `elliptic` curve operations through this plugin
**Review date:** 2026-05-05
**Reviewed by:** Hugh Hackman (VP Engineering Operations)
+19 -4
View File
@@ -1,5 +1,5 @@
version: "0.1.3"
name: kube-vip
version: "1.0.2"
name: headlamp-kube-vip
displayName: kube-vip
createdAt: "2026-03-04T00:00:00Z"
description: >-
@@ -25,7 +25,22 @@ maintainers:
provider:
name: privilegedescalation
annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-kube-vip-plugin/releases/download/v0.1.3/kube-vip-0.1.3.tar.gz"
headlamp/plugin/archive-checksum: sha256:f57ea060c0c276c5cfdff2664265f31f69f00ad88f7c1f4a26702a2a9fbdc76e
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-kube-vip-plugin/releases/download/v1.0.2/kube-vip-1.0.2.tar.gz"
headlamp/plugin/archive-checksum: sha256:cb6b8b6d93a41c129304c57ed705cdafbcb4d6e7511ce5bad0aa05d5762c3fbf
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/distro-compat: "in-cluster"
changes:
- kind: changed
description: "Fix ArtifactHub checksum for v1.0.0 release tarball"
- kind: added
description: "v1.0.0 stable release"
- kind: changed
description: "Bump version from 0.1.5 to 1.0.0"
- kind: changed
description: "Add missing devDependencies: @mui/material, @types/react, @types/react-dom, notistack; pin vitest to ^3.2.4"
- kind: changed
description: "Extend Renovate config from org-level preset"
- kind: changed
description: "Add pinDigests for GitHub Actions SHA pinning"
- kind: changed
description: "Add dual-approval caller workflow"
+6
View File
@@ -0,0 +1,6 @@
# Artifact Hub repository metadata
# https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml
repositoryID: 2f3e67f2-3799-4018-b4bb-84dd2fe1ab9b
owners:
- name: privilegedescalation
email: privilegedescalation@users.noreply.github.com
+20
View File
@@ -0,0 +1,20 @@
{
// Allowlist for inherited dev-dependency CVEs from @kinvolk/headlamp-plugin
// CTO decision (PRI-854): these high-severity vulns are dev/build-time only,
// trace to @kinvolk/headlamp-plugin transitive deps (Picomatch, Vite, lodash),
// and do NOT ship in production plugin artifacts.
"allowlist": [
{
"id": "GHSA-hhpm-516h-p3p6",
"reason": "Picomatch ReDoS: devDependency only, does not ship in production plugin bundle"
},
{
"id": "GHSA-36xf-7xpp-53w5",
"reason": "Vite arbitrary file read: devDependency only, does not ship in production plugin bundle"
},
{
"id": "GHSA-jf8v-p3pp-93qh",
"reason": "lodash code injection via _.template: devDependency only, does not ship in production plugin bundle"
}
]
}
@@ -0,0 +1,66 @@
# ADR 001: React Context for Centralized State
**Status**: Accepted
**Date**: 2026-03-05
**Deciders**: Development Team
---
## Context
The kube-vip plugin shares data across 4 page views (Overview, Services, Nodes, Config) and 1 detail view section (Service). Data comes from standard Kubernetes resources:
- **Services** (via `useList()`)
- **Nodes** (via `useList()`)
- **DaemonSet** (via `ApiProxy.request()`)
- **Pods** (with fallback, via `ApiProxy.request()`)
- **Leases** (`coordination.k8s.io/v1`, via `ApiProxy.request()`)
- **ConfigMap** (via `ApiProxy.request()`)
The context exposes 12 fields: `kubeVipInstalled`, `daemonSetStatus`, `kubeVipPods`, `cloudProviderPods`, `loadBalancerServices`, `nodes`, `leases`, `ipPools`, `configMapData`, `kubeVipConfig`, `loading`, `error`, and `refresh`.
The plugin uses dual-track fetching: Headlamp `useList()` for Services and Nodes, `ApiProxy.request()` for everything else.
---
## Decision
Use a single `KubeVipDataProvider` React Context wrapping all routes and the Service detail section registration. All kube-vip state is computed in the provider and made available to consumers via the `useKubeVipData()` hook.
---
## Consequences
### Positive
- ✅ Single fetch location eliminates duplicate API calls across views
- ✅ Consistent data across all views at any point in time
- ✅ Derived state (e.g., `ipPools` from ConfigMap) computed once in the provider
-`refresh()` function updates everything in a single call
### Negative
- ⚠️ All consumers re-render on any change to any of the 12 fields
- ⚠️ Complex provider component managing 12 fields and multiple fetch strategies
These negatives are mitigated by the fact that kube-vip state changes infrequently (VIP assignments and pool configurations are relatively stable), so unnecessary re-renders are rare in practice.
---
## Alternatives Considered
1. **Per-page fetching** — Rejected. Would duplicate DaemonSet, ConfigMap, and Lease fetching across multiple pages, leading to redundant API calls and potential data inconsistency between views.
2. **Multiple contexts** (e.g., separate contexts for pods, services, config) — Rejected. Data is heavily cross-referenced: `kubeVipInstalled` depends on the DaemonSet, pod discovery depends on labels from the DaemonSet, and IP pool utilization requires both ConfigMap data and Service annotations. Splitting contexts would require complex inter-context dependencies.
3. **External state library** (e.g., Redux, Zustand) — Rejected. External state management libraries are not available in the Headlamp plugin runtime environment.
---
## Changelog
| Date | Change |
| ---- | ------ |
| 2026-03-05 | Initial decision recorded |
@@ -0,0 +1,63 @@
# ADR 002: Annotation-Based State Without CRDs
**Status**: Accepted
**Date**: 2026-03-05
**Deciders**: Development Team
---
## Context
kube-vip does not define any Custom Resource Definitions. All kube-vip state is inferred from standard Kubernetes resources:
- **DaemonSet status** indicates whether kube-vip is installed
- **`kube-vip.io/*` annotations on Services** indicate VIP configuration (requested IP, interface, load balancer class)
- **Lease holder identity** indicates leader election status
- **ConfigMap data** contains IP pool configuration
The plugin must extract kube-vip-specific state from these standard resources without relying on any custom APIs or CRDs.
---
## Decision
Parse kube-vip state entirely from annotations on standard Kubernetes resources and ConfigMap data. No CRDs are queried. Annotation parsing functions in `k8s.ts` extract VIP configuration from Service annotations (e.g., `kube-vip.io/vipAddress`, `kube-vip.io/loadbalancerIPs`). IP pool configuration is parsed from the `kubevip` ConfigMap via `parseIPPools()`. Leader election status is read from Lease `.spec.holderIdentity`.
---
## Consequences
### Positive
- ✅ No CRD dependency — works with any kube-vip installation regardless of version or configuration
- ✅ Uses only standard Kubernetes resources accessible via the standard API
- ✅ Annotation parsing is implemented as pure functions, making it straightforward to test
- ✅ Works across all kube-vip versions that follow the annotation conventions
### Negative
- ⚠️ Annotation schema is implicit — there is no API validation or schema enforcement for annotation values
- ⚠️ Annotations may change across kube-vip versions without notice, since they are not part of a formal API contract
- ⚠️ Parsing logic must handle missing or malformed annotations gracefully
These negatives are mitigated by defensive parsing with fallbacks for missing annotations and sensible defaults when annotation values are absent or unparseable.
---
## Alternatives Considered
1. **kube-vip CRDs** (if they existed) — Not available. kube-vip does not define any Custom Resource Definitions, so there are no custom resources to query.
2. **kube-vip API server** (if it existed) — Not available. kube-vip does not expose a dedicated API server or endpoint for querying its state.
3. **Parse kube-vip pod logs** — Rejected. Log parsing is inherently fragile, depends on log format stability, and requires log access RBAC permissions that may not be available in all environments.
---
## Changelog
| Date | Change |
| ---- | ------ |
| 2026-03-05 | Initial decision recorded |
@@ -0,0 +1,66 @@
# ADR 003: Static Pod Discovery with Label Selector Fallback
**Status**: Accepted
**Date**: 2026-03-05
**Deciders**: Development Team
---
## Context
kube-vip can be deployed in two ways:
1. **DaemonSet deployment**: Pods are managed by a DaemonSet and have standard labels (e.g., `app: kube-vip`) that can be used for label-selector based discovery.
2. **Static pod deployment**: Pod manifests are placed in `/etc/kubernetes/manifests/` on each node. Static pods do not have the same labels as DaemonSet-managed pods and follow the naming convention `kube-vip-<node-name>`.
The plugin needs to discover kube-vip pods regardless of the deployment method. The pod fetch uses a two-level fallback strategy: first try label-selector based discovery, then fall back to listing all pods in `kube-system` and filtering by name prefix.
---
## Decision
Implement a two-level pod discovery fallback:
1. **Primary**: Fetch pods using label selector (matches DaemonSet-deployed kube-vip).
2. **Fallback**: If the label-selector fetch fails or returns empty, list all pods in the `kube-system` namespace and filter by `name.startsWith('kube-vip')` (matches static pod naming convention).
This covers both deployment methods without requiring user configuration.
---
## Consequences
### Positive
- ✅ Works with both DaemonSet and static pod deployments out of the box
- ✅ No user configuration needed — deployment method is auto-detected
- ✅ Automatic fallback is transparent to the user and other plugin components
### Negative
- ⚠️ Fallback fetches all `kube-system` pods, which is a broader query than necessary
- ⚠️ Name-prefix matching (`kube-vip`) is convention-dependent and could produce false positives if other pods share the prefix
These negatives are mitigated by the fact that `kube-system` typically has a manageable number of pods, and the name prefix `kube-vip` is the standard convention used by the kube-vip project.
---
## Alternatives Considered
1. **Label selector only** — Rejected. Would miss static pod deployments entirely, leaving users who deploy kube-vip as static pods without visibility.
2. **Name prefix only** — Rejected. Less efficient than label selector for DaemonSet deployments, as it requires listing all pods in the namespace rather than using server-side filtering.
3. **User-configured discovery method** — Rejected. Adds unnecessary configuration burden for something that can be reliably auto-detected through the fallback strategy.
4. **Node filesystem check for static pod manifests** — Rejected. Requires node-level filesystem access, which is not available to Headlamp plugins running in the browser.
---
## Changelog
| Date | Change |
| ---- | ------ |
| 2026-03-05 | Initial decision recorded |
@@ -0,0 +1,64 @@
# ADR 004: Comprehensive Component-Level Testing with Shared Helpers
**Status**: Accepted
**Date**: 2026-03-05
**Deciders**: Development Team
---
## Context
The plugin has extensive test coverage including both unit tests (`k8s.ts`, `KubeVipDataContext`) and component render tests (`ConfigPage`, `NodesPage`, `OverviewPage`, `ServiceDetailSection`, `ServicesPage`). Component tests need to mock both Headlamp's API and CommonComponents.
A shared `test-helpers.tsx` file provides fixture factories, and a `__mocks__/commonComponents.ts` file provides stub implementations of Headlamp's CommonComponents for render testing.
---
## Decision
Implement comprehensive component-level testing with two shared test infrastructure files:
1. **`test-helpers.tsx`**: Shared fixture factories for creating test data (mock Services, Nodes, Pods, DaemonSets, ConfigMaps, Leases). Provides consistent test data across all test files.
2. **`__mocks__/commonComponents.ts`**: Stub implementations of Headlamp CommonComponents (`SectionBox`, `SimpleTable`, `StatusLabel`, `NameValueTable`) that render as simple HTML elements for testing.
All component tests render with a `KubeVipDataProvider` wrapper using mocked context values, verifying the component renders correct data from context.
---
## Consequences
### Positive
- ✅ Consistent test fixtures across all test files eliminate divergent test data
- ✅ CommonComponents mocks enable render testing without the full Headlamp runtime
- ✅ Comprehensive coverage of component rendering logic and context integration
- ✅ Easy to add new component tests by reusing existing fixtures and mocks
### Negative
- ⚠️ Mock maintenance burden when Headlamp's CommonComponents API changes
- ⚠️ Test helpers may diverge from actual API shapes over time if not kept in sync
These negatives are mitigated by type-checking test helpers against the actual interfaces, ensuring compile-time detection of API shape mismatches.
---
## Alternatives Considered
1. **Unit tests only** (no component rendering) — Rejected. Misses rendering bugs and context integration issues that only surface when components are mounted with real context values.
2. **E2E tests only** — Rejected. Too slow for rapid development feedback and requires a running Headlamp instance with a connected cluster.
3. **Inline mocks per test file** — Rejected. Leads to inconsistent and duplicated test setup across files, making it harder to maintain and update when APIs change.
4. **Storybook for component testing** — Rejected. Additional tooling overhead is not justified for the scope of this plugin, and Storybook does not easily integrate with Headlamp's plugin component model.
---
## Changelog
| Date | Change |
| ---- | ------ |
| 2026-03-05 | Initial decision recorded |
+39
View File
@@ -0,0 +1,39 @@
# Architecture Decision Records
## What is an ADR?
An Architecture Decision Record (ADR) is a document that captures an important architectural decision made along with its context and consequences. ADRs are used to record the motivation behind decisions so that future team members can understand why certain choices were made.
## Format
This project uses the Nygard-style ADR format:
- **Title**: Short noun phrase describing the decision
- **Status**: Proposed, Accepted, Deprecated, or Superseded
- **Context**: The forces at play, including technical, political, social, and project-specific
- **Decision**: The change that is being proposed or has been agreed upon
- **Consequences**: What becomes easier or harder as a result of this decision
- **Alternatives Considered**: Other options that were evaluated
## Index
| ADR | Title | Status | Date |
| --- | ----- | ------ | ---- |
| [001](001-react-context-state.md) | React Context for Centralized State | Accepted | 2026-03-05 |
| [002](002-annotation-based-state.md) | Annotation-Based State Without CRDs | Accepted | 2026-03-05 |
| [003](003-static-pod-discovery.md) | Static Pod Discovery with Label Selector Fallback | Accepted | 2026-03-05 |
| [004](004-component-testing-strategy.md) | Comprehensive Component-Level Testing with Shared Helpers | Accepted | 2026-03-05 |
## Creating New ADRs
1. Copy an existing ADR as a template
2. Assign the next sequential number (e.g., `005-your-decision.md`)
3. Fill in all sections following the Nygard-style format
4. Set the status to `Proposed` until the team has reviewed and accepted
5. Update the index table in this README
6. Add a changelog entry at the bottom of the ADR
## References
- [Michael Nygard's article on ADRs](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions)
- [ADR GitHub organization](https://adr.github.io/)
-18451
View File
File diff suppressed because it is too large Load Diff
+30 -2
View File
@@ -1,6 +1,6 @@
{
"name": "kube-vip",
"version": "0.1.3",
"version": "1.0.2",
"description": "Headlamp plugin for kube-vip virtual IP and load balancer visibility",
"repository": {
"type": "git",
@@ -12,6 +12,7 @@
"homepage": "https://github.com/privilegedescalation/headlamp-kube-vip-plugin#readme",
"author": "privilegedescalation",
"license": "Apache-2.0",
"packageManager": "pnpm@10.32.1",
"scripts": {
"start": "headlamp-plugin start",
"build": "headlamp-plugin build",
@@ -24,7 +25,34 @@
"test": "vitest run",
"test:watch": "vitest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3",
"lodash": ">=4.18.0",
"vite": ">=6.4.2",
"elliptic": ">=6.6.1"
},
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0"
"@headlamp-k8s/eslint-config": "^0.6.0",
"@kinvolk/headlamp-plugin": "^0.13.0",
"@mui/material": "^5.15.14",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"eslint": "^8.57.0",
"jsdom": "^24.0.0",
"notistack": "^3.0.0",
"prettier": "^2.8.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^5.3.0",
"typescript": "~5.6.2",
"vitest": "^3.2.4"
}
}
+11990
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,4 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"]
"extends": ["github>privilegedescalation/.github:renovate-config"]
}
+4 -1
View File
@@ -21,6 +21,7 @@ import {
isEgressEnabled,
isKubeVipService,
isPodReady,
KUBE_VIP_NAMESPACE,
phaseToStatus,
} from '../api/k8s';
import { useKubeVipContext } from '../api/KubeVipDataContext';
@@ -105,7 +106,9 @@ export default function OverviewPage() {
{
name: 'Status',
value: (
<StatusLabel status="error">No kube-vip pods found in kube-system</StatusLabel>
<StatusLabel status="error">
No kube-vip pods found in {KUBE_VIP_NAMESPACE}
</StatusLabel>
),
},
{
+1 -1
View File
@@ -2,7 +2,7 @@
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
"compilerOptions": {
"module": "esnext",
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "@testing-library/jest-dom"]
"types": ["vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src"]
}
+3
View File
@@ -1,6 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
define: {
'process.env.NODE_ENV': '"test"',
},
test: {
globals: true,
environment: 'jsdom',