Compare commits

...

62 Commits

Author SHA1 Message Date
Chris Farhood f896622b5b fix: add markdownlint config and auto-fix markdown errors
- Add .markdownlint-cli2.jsonc based on headlamp-agent-skills config
- Disable MD013 (line length), MD041 (first-line-heading), MD036
  (emphasis-as-heading), MD024 (no-duplicate-heading), MD040
  (fenced-code-language), MD060 (table-column-style), MD029
  (ol-prefix), MD033 (no-inline-html) — appropriate for plugin docs
- Run markdownlint-cli2 --fix to auto-fix MD022, MD031, MD032
- Manually fix remaining code block language annotations

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 04:54:28 +00:00
Chris Farhood a65743dea3 fix(e2e): grant CI runner read access to polaris namespace for RBAC pre-flight check
The RBAC pre-flight check workflow step (commit 46350c5) verifies that
polaris-dashboard-proxy-reader Role and RoleBinding exist in the polaris
namespace before running E2E tests. However, the CI runner's RBAC
(e2e-ci-runner-role in privilegedescalation-dev) did not include
permission to read roles/rolebindings in the polaris namespace, causing
the pre-flight check to fail with a generic kubectl error on all branches.

Fix: add rules to e2e-ci-runner-role allowing get on roles/rolebindings in
privilegedescalation-dev (for the pre-flight check itself), plus a new
Role + RoleBinding in the polaris namespace granting the runner read
access to rbac resources there.

Without this fix, the pre-flight check exits 1 on every branch until someone
SSHs into the runner pod and manually applies the polaris RBAC manifest —
which they shouldn't need to do.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-03 15:13:03 +00:00
privilegedescalation-engineer[bot] dff1265435 fix: pass pr_number to dual-approval-check workflow (#119)
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:33:19 +00:00
privilegedescalation-ceo[bot] 7c58826668 Merge pull request #117 from privilegedescalation/ci/e2e-deploy-diagnostics
ci(e2e): add deployment diagnostics step on failure
2026-03-24 22:26:32 +00:00
privilegedescalation-engineer[bot] 4edc829b3f ci(e2e): add deployment diagnostics step on failure
When the E2E deploy step fails (rollout timeout, pod not ready, etc.),
previously required manual cluster investigation to diagnose the root
cause. This heartbeat had to grep CI logs and query kubectl separately
to determine a :latest image drift issue.

The new step captures pod state, pod describe output, and recent namespace
events immediately when a failure occurs — surfacing the root cause
directly in the CI run log.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:57:58 +00:00
privilegedescalation-ceo[bot] 8f10be39bd Merge pull request #116 from privilegedescalation/fix/pin-headlamp-version-e2e
fix(e2e): pin Headlamp image to v0.40.1 instead of :latest
2026-03-24 21:42:51 +00:00
privilegedescalation-engineer[bot] 27212a91e1 fix(e2e): pin Headlamp image to v0.40.1 instead of :latest
The :latest tag caused E2E flakiness when a newer Headlamp image was
pulled on some cluster nodes (IfNotPresent policy) but not others.
Concurrent E2E runs on main saw different image versions, and the newest
:latest (sha256:89c6c65) failed to pass the readiness probe within 120s.

Pin to v0.40.1 — the same version running in production (kube-system) —
so all nodes use the same cached digest and CI is deterministic. Update
this pin when Headlamp is upgraded in production.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:28:38 +00:00
privilegedescalation-ceo[bot] 7b72306133 Merge pull request #109 from privilegedescalation/feat/renovate-extend-org-config
feat: extend Renovate config from org-level preset
2026-03-24 18:45:58 +00:00
privilegedescalation-ceo[bot] e16e6255d0 Merge pull request #110 from privilegedescalation/ci/e2e-concurrency-guard
ci: add concurrency guard to E2E workflow
2026-03-24 18:45:55 +00:00
privilegedescalation-ceo[bot] 4beb0c4d0e Merge pull request #113 from privilegedescalation/fix/e2e-clean-deploy
fix(e2e): clean-delete existing deployment before redeploy for guaranteed fresh pod
2026-03-24 18:45:52 +00:00
Gandalf the Greybeard 175d3ec6a2 fix(e2e): clean-delete existing deployment before redeploy for guaranteed fresh pod
kubectl apply without prior deletion patches in place: if the pod spec is
unchanged between runs, no rollout is triggered and a potentially degraded
pod from a prior run keeps serving. This caused the auth.setup.ts timeout
(waiting for the "use a token" button) even when no concurrent runs were
present — the headlamp-e2e pod was in an inconsistent state from a previous
run that didn't tear down cleanly.

Changes:
- deploy-e2e-headlamp.sh: delete Deployment, Service, and ServiceAccount
  (with --wait) before applying, guaranteeing a fresh pod each run
- auth.setup.ts: add explicit waitFor({ state: 'visible', timeout: 15_000 })
  before the "use a token" button click, so failures surface at 15 s with a
  clear locator error rather than silently timing out at 60 s

Fixes the pre-existing infra issue blocking PR#110.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 16:40:30 +00:00
privilegedescalation-engineer[bot] e63cd03267 fix(e2e): use cancel-in-progress: false to prevent dangling cluster resources
cancel-in-progress: true would cancel in-flight E2E runs when a new one
arrives. GitHub Actions does not guarantee that if: always() steps run on
cancelled jobs, so teardown-e2e-headlamp.sh may be skipped — leaving the
headlamp-e2e Deployment/Service/ConfigMap dangling in privilegedescalation-dev.

Switching to false (queue) ensures the running job always completes its
teardown before the next run starts.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:34:36 +00:00
privilegedescalation-engineer[bot] 4d878c8737 ci: add concurrency guard to E2E workflow
Prevents parallel E2E runs from conflicting over the shared
headlamp-e2e Helm release in privilegedescalation-dev. With
cancel-in-progress: true, a new push cancels any in-progress
run on the same repo — only one E2E suite runs at a time.

Observed failure: PR#109 and PR#108 ran concurrently and the
auth setup in PR#109 timed out, likely due to resource contention
on the shared headlamp-e2e instance.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:27:52 +00:00
privilegedescalation-ceo[bot] 5f817ec4f6 Merge pull request #108 from privilegedescalation/fix/node24-action-versions
ci: upgrade e2e.yaml actions to Node.js 24-compatible versions
2026-03-24 16:25:26 +00:00
Hugh Hackman 490807cef6 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:15 +00:00
Hugh Hackman 06d7dfb212 ci: upgrade e2e.yaml actions to Node.js 24-compatible versions
Update action versions ahead of GitHub's June 2, 2026 Node.js 20 deprecation:

- actions/setup-node@v4 → @v6
- actions/upload-artifact@v4 → @v7

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:11:05 +00:00
privilegedescalation-engineer[bot] ba508b8fc4 release: v1.0.0 (#107)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-24 15:27:35 +00:00
privilegedescalation-ceo[bot] b928fff4a5 release: v1.0.0
release: v1.0.0
2026-03-22 19:19:53 +00:00
Gandalf the Greybeard df6a5967ea fix(changelog): remove false coverage claim and fix compare link
- Remove "Coverage threshold: Vitest coverage threshold enforced in CI
  (#82)" — the shared CI workflow does not run coverage; this line was
  inaccurate.
- Fix [1.0.0] compare link from v0.6.0...v1.0.0 to v0.7.2...v1.0.0
  to accurately reflect the last tagged release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 15:17:11 +00:00
Gandalf the Greybeard 415e32cdc9 release: v1.0.0
- Bump version to 1.0.0 in package.json and package-lock.json
- Update artifacthub-pkg.yml: version, archive-url, and changes section
- Add v1.0.0 CHANGELOG entry covering changes since v0.7.2

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 11:58:44 +00:00
privilegedescalation-engineer[bot] aa32e7a353 ci: add packageManager field to pin pnpm version (#103)
* ci: add packageManager field to pin pnpm version

Pins pnpm@10.32.1 via the packageManager field. This allows
pnpm/action-setup@v4 to resolve the version from package.json instead
of relying on `version: latest`, preventing silent breakage on major
pnpm version bumps.

Fixes: PRI-674

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

* ci: trigger fresh CI run after Corepack fix merges in .github PR #57

The shared plugin-ci.yaml now uses Corepack when packageManager field
is set, avoiding the 'Multiple versions of pnpm specified' error.

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

* ci: retrigger CI with updated shared workflow (python3 pnpm detection)

---------

Co-authored-by: Hugh Hackman <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: privilegedescalation-ceo[bot] <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com>
Co-authored-by: Hugh Hackman <hugh@privilegedescalation.paperclip.ing>
Co-authored-by: Hugh Hackman <hugh@privilegedescalation.com>
2026-03-22 11:17:41 +00:00
privilegedescalation-ceo[bot] 67bfe5ff5c Merge pull request #105 from privilegedescalation/chore/renovate-pin-digests
chore(renovate): add pinDigests for GitHub Actions SHA pinning
2026-03-22 11:06:32 +00:00
privilegedescalation-engineer[bot] c08f3fbdbe chore(renovate): add pinDigests to ensure SHA pinning for GitHub Actions
The org renovate-config.json (PR #63) adds pinDigests: true at the org level,
but this repo extends config:recommended directly. Adding pinDigests: true here
ensures GitHub Actions are pinned to full commit SHAs regardless of whether the
org config is extended.

Related: privilegedescalation/.github#63, PRI-757
2026-03-22 07:16:04 +00:00
privilegedescalation-ceo[bot] 02dc79b739 Merge pull request #98 from privilegedescalation/feat/dual-approval-status-check
ci: add dual-approval status check (CTO + QA)
2026-03-22 05:58:29 +00:00
Hugh Hackman d1097c2dbf ci: trigger fresh CI run with updated shared workflows 2026-03-22 05:49:14 +00:00
privilegedescalation-ceo[bot] 5fa14ab353 Merge pull request #104 from privilegedescalation/fix/e2e-dns-readiness-check
fix: wait for HTTP reachability after rollout in deploy-e2e-headlamp.sh
2026-03-22 05:24:23 +00:00
Hugh Hackman acd53c297b fix: wait for HTTP reachability after rollout in deploy-e2e-headlamp.sh
kubectl rollout status confirms the pod is ready per readinessProbe, but
Kubernetes Service DNS propagation to the runner pod may lag behind.
This caused intermittent E2E failures with ERR_NAME_NOT_RESOLVED.

Add a poll loop (max 120s) after rollout status that verifies the service
URL is reachable via HTTP before writing .env.e2e. This eliminates the
race condition between DNS propagation and Playwright launch.

Fixes: PRI-687 (intermittent E2E DNS failure)
2026-03-22 04:51:30 +00:00
privilegedescalation-engineer[bot] fd66b119b3 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-22 04:41:32 +00:00
privilegedescalation-ceo[bot] 21026cc992 Merge pull request #102 from privilegedescalation/fix/add-eslint-direct-dep
fix: add eslint as direct devDependency
2026-03-22 04:38:32 +00:00
Gandalf the Greybeard 95096562e4 fix: add @headlamp-k8s/eslint-config as direct devDependency
pnpm strict mode does not hoist transitive deps. @headlamp-k8s/eslint-config
was only available via @kinvolk/headlamp-plugin, causing ESLint to fail with
"Cannot find config @headlamp-k8s/eslint-config to extend from". Adding it
as a direct devDependency makes it accessible at the root node_modules level.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 04:29:31 +00:00
Hugh Hackman 62baf2bd5e fix: also add prettier as direct devDependency
pnpm strict mode does not expose transitive dependency binaries.
Adding prettier@^2.8.8 alongside eslint@^8.57.0 so that
pnpm run format:check works in CI without 'prettier: not found'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 04:24:45 +00:00
Hugh Hackman d2da09406a fix: add eslint as direct devDependency
pnpm's strict node_modules layout does not expose transitive dependency
binaries. eslint was only a transitive dep via @kinvolk/headlamp-plugin,
causing 'eslint: not found' when running pnpm run lint in CI.

Adding eslint@^8.57.0 as a direct devDependency ensures the binary is
available in node_modules/.bin/ under pnpm.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 04:21:28 +00:00
privilegedescalation-ceo[bot] a975192dfb Merge pull request #92 from privilegedescalation/fix/npm-audit-vulnerabilities
fix: patch 8/9 npm audit vulnerabilities via pnpm.overrides
2026-03-21 23:45:32 +00:00
Gandalf the Greybeard 2c80d0451e fix: patch 8 of 9 npm vulnerabilities via pnpm.overrides
Move vulnerability overrides from npm-format top-level `overrides` to
pnpm-format `pnpm.overrides`. Add flatted override to patch the
high-severity prototype pollution CVE. All 5 high + 3 moderate severity
issues are now resolved.

Remaining: elliptic (low, no patch available upstream).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 22:41:32 +00:00
privilegedescalation-ceo[bot] d4a4e9a355 Merge pull request #95 from privilegedescalation/fix/add-typescript-devdep
fix: add typescript as explicit devDependency
2026-03-21 22:39:17 +00:00
privilegedescalation-ceo[bot] a08c0fc368 Merge branch 'main' into fix/add-typescript-devdep 2026-03-21 22:35:56 +00:00
privilegedescalation-ceo[bot] d0a6794576 Merge pull request #97 from privilegedescalation/fix/e2e-token-auth
fix: use token auth in E2E — handle direct /token redirect
2026-03-21 22:35:45 +00:00
Hugh Hackman 00c270b0d4 fix: use token auth in E2E workflow, handle direct /token redirect
The E2E Headlamp instance is deployed without OIDC configuration, so
Headlamp redirects / → /token directly instead of / → /login. The
authenticateWithToken function was hardcoded to expect /login first,
causing a 60s timeout on every run.

- e2e.yaml: remove unused Setup Helm step (deploy script uses kubectl)
- e2e.yaml: remove AUTHENTIK_USERNAME/PASSWORD (no OIDC in E2E instance)
- auth.setup.ts: waitForURL accepts both /login and /token; only clicks
  "use a token" if landed on /login (OIDC-configured Headlamp)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:55:44 +00:00
privilegedescalation-ceo[bot] 7f115e0d6e Merge pull request #94 from privilegedescalation/fix/e2e-kubectl-deploy
fix: replace Helm-based E2E deploy with kubectl apply
2026-03-21 20:51:01 +00:00
Gandalf the Greybeard 9d02f504fd fix: add typescript as explicit devDependency
pnpm run tsc failed with "tsc not found" because typescript was only
available as a transitive dependency from @kinvolk/headlamp-plugin.
Adding it explicitly as a direct devDependency ensures tsc is always
accessible regardless of pnpm hoisting behavior.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 20:45:46 +00:00
Gandalf the Greybeard 65c25067ec fix: replace Helm-based E2E deploy with kubectl apply
The Helm chart deployment was consistently failing — the pod enters
CrashLoopBackOff despite identical kubectl manifests working. The Helm
chart also silently ignored extraVolumes/extraVolumeMounts (pnpm-style
keys not supported by the chart), meaning the plugin ConfigMap was
never actually mounted even when deploy appeared to succeed.

Replace with direct kubectl apply using a bash heredoc to render the
manifest with shell variable substitution. This removes the Helm
dependency, fixes the plugin volume mount, and uses the exact
configuration that was proven to work in the cluster.

Also adds explicit initialDelaySeconds/failureThreshold on readiness
and liveness probes to give Headlamp adequate startup time.

Note: .github/workflows/e2e.yaml still has a Setup Helm step that is
now unused — assigned to Hugh Hackman to remove.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 20:43:25 +00:00
privilegedescalation-ceo[bot] 4c6324c4c2 Merge pull request #89 from privilegedescalation/fix/e2e-namespace-privilegedescalation-dev
fix: move E2E test namespace from default to privilegedescalation-dev
2026-03-21 20:17:41 +00:00
Hugh Hackman ca4832bcc3 fix: add watch verb to services/serviceaccounts/configmaps/secrets in RBAC
Helm --wait requires watch on these resources to track rollout readiness.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:10:32 +00:00
Hugh Hackman d6c8a8bbfc fix: disable ClusterRoleBinding creation in E2E Helm values
The Headlamp chart defaults to creating a ClusterRoleBinding, but the
ARC runner service account lacks cluster-scoped RBAC permissions. E2E
tests only need Headlamp to serve the UI — no cluster-admin required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:08:25 +00:00
Hugh Hackman 3d91572b59 fix: update Headlamp Helm repo URL to kubernetes-sigs
The Headlamp project moved from headlamp-k8s to kubernetes-sigs GitHub org.
The old chart URL https://headlamp-k8s.github.io/headlamp/ now returns 404.
Updated to https://kubernetes-sigs.github.io/headlamp/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:05:08 +00:00
Hugh Hackman f0f3bd51a4 ci: change E2E_NAMESPACE from default to privilegedescalation-dev
Align workflow with org RBAC policy — agents have read-write access only
in privilegedescalation-dev, not the default namespace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:01:04 +00:00
Gandalf the Greybeard 6e9c97593c fix: move E2E test namespace from default to privilegedescalation-dev
Per org RBAC policy, development/testing Headlamp instances must run in
`privilegedescalation-dev`, not `default`. Agents only have read-write
access in `privilegedescalation` and `privilegedescalation-dev` — the
`default` namespace is outside our permitted scope.

Updated:
- deployment/e2e-ci-runner-rbac.yaml: Role/RoleBinding now targets privilegedescalation-dev
- deployment/headlamp-e2e-values.yaml: comment updated
- scripts/deploy-e2e-headlamp.sh: default namespace changed
- scripts/teardown-e2e-headlamp.sh: default namespace changed

Note: .github/workflows/e2e.yaml still sets E2E_NAMESPACE: default and
needs a separate update — delegated to Hugh Hackman (workflow owner).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:51:18 +00:00
privilegedescalation-engineer[bot] a5398e8409 feat: add ExemptionManager tests, coverage threshold, and ArtifactHub metadata polish (#82)
* ci: rework E2E infrastructure to use default namespace

Board directive: E2E tests must run in the `default` namespace.
Nothing should persist beyond a test run; no dedicated namespace needed.

Changes:
- e2e-ci-runner-rbac.yaml: retarget Role/RoleBinding to `default`,
  remove ClusterRole/ClusterRoleBinding (no longer needed since we
  don't need cluster-scoped namespace read permission)
- e2e.yaml: set E2E_NAMESPACE=default
- deploy-e2e-headlamp.sh: default namespace to `default`, remove
  namespace existence check (default always exists)
- teardown-e2e-headlamp.sh: default namespace to `default`, remove
  namespace existence check guard
- headlamp-e2e-values.yaml: update usage comment
- e2e/README.md: remove namespace creation prerequisite

Closes #78 #79

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

* ci: add RBAC preflight check to deploy-e2e-headlamp.sh

Fails fast with a clear error and remediation hint if the runner SA
lacks configmap delete permission, instead of dying mid-deploy.

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

* feat: add ExemptionManager tests, coverage threshold, and ArtifactHub metadata

- Add 22 unit tests for ExemptionManager.tsx covering:
  - Failing checks extraction (pod-level, container-level, ignore-severity, dedup)
  - Dialog open/close, check toggle, exempt-all toggle
  - Apply button enabled/disabled state
  - ApiProxy.request called with correct path (apps/batch/core) and annotation structure
  - Success and error feedback states, in-flight "Applying..." label
- Add vitest coverage config with >=80% threshold (lines/functions/branches/statements)
- Update artifacthub-pkg.yml:
  - Add install section (Headlamp-native plugin installer only)
  - Add appVersion: "5.0" (compatible Polaris dashboard version)
  - Expand distro-compat from "in-cluster" to "in-cluster,web,desktop"
  - Add changes block documenting v1.0 features

Closes privilegedescalation/headlamp-polaris-plugin#81 (partial — test and metadata tasks)

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

* style: fix Prettier formatting in ExemptionManager.test.tsx

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

* fix: add @vitest/coverage-v8 devDependency for coverage provider

vitest.config.mts specifies coverage.provider: 'v8' but the
@vitest/coverage-v8 package was missing from devDependencies.

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

---------

Co-authored-by: Hugh Hackman <hugh@privilegedescalation.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.com>
Co-authored-by: Samuel Stinkpost <samuel@privilegedescalation.dev>
Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.dev>
2026-03-21 12:53:07 +00:00
privilegedescalation-ceo[bot] bb1df5f3f6 Merge pull request #80 from privilegedescalation/ci/e2e-default-namespace
ci: rework E2E infrastructure to use default namespace
2026-03-21 03:26:13 +00:00
Hugh Hackman 1bf5c2431c ci: add RBAC preflight check to deploy-e2e-headlamp.sh
Fails fast with a clear error and remediation hint if the runner SA
lacks configmap delete permission, instead of dying mid-deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 03:15:06 +00:00
Hugh Hackman 08a3009ba8 ci: rework E2E infrastructure to use default namespace
Board directive: E2E tests must run in the `default` namespace.
Nothing should persist beyond a test run; no dedicated namespace needed.

Changes:
- e2e-ci-runner-rbac.yaml: retarget Role/RoleBinding to `default`,
  remove ClusterRole/ClusterRoleBinding (no longer needed since we
  don't need cluster-scoped namespace read permission)
- e2e.yaml: set E2E_NAMESPACE=default
- deploy-e2e-headlamp.sh: default namespace to `default`, remove
  namespace existence check (default always exists)
- teardown-e2e-headlamp.sh: default namespace to `default`, remove
  namespace existence check guard
- headlamp-e2e-values.yaml: update usage comment
- e2e/README.md: remove namespace creation prerequisite

Closes #78 #79

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:40:47 +00:00
privilegedescalation-ceo[bot] b3f1f65b2f Merge pull request #73 from privilegedescalation/gandalf/e2e-redesign-custom-image
refactor: redesign E2E to use ConfigMap volume mount with stock Headlamp image
2026-03-21 00:09:09 +00:00
Gandalf the Greybeard 74a5bb0a01 fix: teardown-e2e-headlamp.sh gracefully skips missing namespace
When the headlamp-e2e namespace does not exist, teardown now exits
early with a clear message instead of failing with a misleading RBAC
error. Addresses PRI-443.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-20 22:45:39 +00:00
Gandalf the Greybeard 9249f151a8 fix: add ClusterRole for runner SA to verify headlamp-e2e namespace
kubectl get namespace is cluster-scoped and requires a ClusterRole.
The runner SA only had a namespaced Role, causing E2E to fail with
Forbidden even when the namespace existed. Adds a minimal ClusterRole
restricted to get on headlamp-e2e only.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-20 22:29:00 +00:00
privilegedescalation-paperclip[bot] dd782fbea0 ci: pass GitHub App token secrets to release workflow (#76)
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:35 +00:00
Hugh Hackman 0a52a8effa fix: remove namespace create/delete from E2E scripts
The CI runner SA only has namespace-scoped RBAC in headlamp-e2e — it
cannot create or delete namespaces at the cluster level. Deploy now
verifies the namespace exists (with a clear error if not), and teardown
cleans up resources without deleting the namespace itself.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-20 01:13:02 +00:00
Hugh Hackman 902f206e32 ci: update E2E workflow for ConfigMap approach
Match deploy-e2e-headlamp.sh changes:
- Remove Docker image build/push steps (no custom images)
- Remove packages:write permission (no GHCR push needed)
- Add kubectl and Helm setup steps
- Deploy script creates ConfigMap from dist/ and uses stock Headlamp image
2026-03-20 01:05:39 +00:00
Gandalf the Greybeard 4344d33349 refactor: replace Dockerfile.e2e with ConfigMap volume mount for E2E plugin loading
Delete custom Docker image approach per board directive. Plugin is now
loaded into stock Headlamp via a ConfigMap volume mount:

- Delete Dockerfile.e2e
- deploy-e2e-headlamp.sh creates a ConfigMap from dist/ and mounts it
  into the stock ghcr.io/headlamp-k8s/headlamp image
- Helm values use extraVolumes/extraVolumeMounts for the ConfigMap
- No custom images, no PVCs, no kubectl exec/cp

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-20 01:01:39 +00:00
Hugh Hackman 8ac890a1c6 ci: update E2E workflow for Docker image approach
Replace PVC/kubectl-patch E2E workflow with the new Docker image approach:
- Build custom Headlamp image with plugin pre-installed (Dockerfile.e2e)
- Push to ghcr.io/privilegedescalation/headlamp-polaris-e2e
- Deploy dedicated instance in headlamp-e2e namespace via Helm
- Auto-generate auth token via deploy-e2e-headlamp.sh
- Teardown after tests (always runs)

No more PVCs, kubectl exec/cp, or kube-system deployment patching.
2026-03-20 01:01:11 +00:00
Gandalf the Greybeard 6189f2b983 refactor: redesign E2E to use custom Docker image instead of PVC/kubectl
Replace the PVC + kubectl-patch approach for E2E plugin deployment with
a custom Docker image that has the plugin pre-installed. This eliminates
all policy-violating operations:

- No PVCs in kube-system
- No kubectl exec/cp to Headlamp pods
- No deployment patching via kubectl
- No temporary pods or ConfigMap-based file transfers

The new approach builds a Headlamp image with the plugin baked in
(Dockerfile.e2e), deploys it as a dedicated instance in the headlamp-e2e
namespace via Helm, and tears it down after tests complete.

RBAC is scoped to the headlamp-e2e namespace instead of kube-system.

Note: .github/workflows/e2e.yaml still needs updating to use the new
scripts — that change is delegated to Hugh (CI/CD owner).

Closes: privilegedescalation/headlamp-polaris-plugin#72

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-20 00:33:09 +00:00
privilegedescalation-paperclip[bot] 4296eb97fb release: v0.7.2 (#70)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-19 21:34:59 +00:00
privilegedescalation-paperclip[bot] 87bf1a321f fix: update e2e runner label to runners-privilegedescalation (#71)
ARC runner scale set label changed from local-ubuntu-latest to
runners-privilegedescalation. The shared workflows were updated in
.github PR #28 but this per-repo e2e workflow was missed.

Co-authored-by: Hugh Hackman <hugh@privilegedescalation.com>
2026-03-19 21:34:45 +00:00
38 changed files with 1428 additions and 405 deletions
+9 -3
View File
@@ -10,6 +10,7 @@ You are an agent installer that helps users browse and install Claude Code agent
## Your Capabilities ## Your Capabilities
You can: You can:
1. List all available agent categories 1. List all available agent categories
2. List agents within a category 2. List agents within a category
3. Search for agents by name or description 3. Search for agents by name or description
@@ -25,20 +26,23 @@ You can:
## Workflow ## Workflow
### When user asks to browse or list agents: ### When user asks to browse or list agents
1. Fetch categories from GitHub API using WebFetch or Bash with curl 1. Fetch categories from GitHub API using WebFetch or Bash with curl
2. Parse the JSON response to extract directory names 2. Parse the JSON response to extract directory names
3. Present categories in a numbered list 3. Present categories in a numbered list
4. When user selects a category, fetch and list agents in that category 4. When user selects a category, fetch and list agents in that category
### When user wants to install an agent: ### When user wants to install an agent
1. Ask if they want global installation (~/.claude/agents/) or local (.claude/agents/) 1. Ask if they want global installation (~/.claude/agents/) or local (.claude/agents/)
2. For local: Check if .claude/ directory exists, create .claude/agents/ if needed 2. For local: Check if .claude/ directory exists, create .claude/agents/ if needed
3. Download the agent .md file from GitHub raw URL 3. Download the agent .md file from GitHub raw URL
4. Save to the appropriate directory 4. Save to the appropriate directory
5. Confirm successful installation 5. Confirm successful installation
### When user wants to search: ### When user wants to search
1. Fetch the README.md which contains all agent listings 1. Fetch the README.md which contains all agent listings
2. Search for the term in agent names and descriptions 2. Search for the term in agent names and descriptions
3. Present matching results 3. Present matching results
@@ -47,6 +51,7 @@ You can:
**User:** "Show me available agent categories" **User:** "Show me available agent categories"
**You:** Fetch from GitHub API, then present: **You:** Fetch from GitHub API, then present:
``` ```
Available categories: Available categories:
1. Core Development (11 agents) 1. Core Development (11 agents)
@@ -57,6 +62,7 @@ Available categories:
**User:** "Install the python-pro agent" **User:** "Install the python-pro agent"
**You:** **You:**
1. Ask: "Install globally (~/.claude/agents/) or locally (.claude/agents/)?" 1. Ask: "Install globally (~/.claude/agents/) or locally (.claude/agents/)?"
2. Download from GitHub 2. Download from GitHub
3. Save to chosen directory 3. Save to chosen directory
+25
View File
@@ -8,12 +8,14 @@ model: sonnet
You are a senior agent organizer with expertise in assembling and coordinating multi-agent teams. Your focus spans task analysis, agent capability mapping, workflow design, and team optimization with emphasis on selecting the right agents for each task and ensuring efficient collaboration. You are a senior agent organizer with expertise in assembling and coordinating multi-agent teams. Your focus spans task analysis, agent capability mapping, workflow design, and team optimization with emphasis on selecting the right agents for each task and ensuring efficient collaboration.
When invoked: When invoked:
1. Query context manager for task requirements and available agents 1. Query context manager for task requirements and available agents
2. Review agent capabilities, performance history, and current workload 2. Review agent capabilities, performance history, and current workload
3. Analyze task complexity, dependencies, and optimization opportunities 3. Analyze task complexity, dependencies, and optimization opportunities
4. Orchestrate agent teams for maximum efficiency and success 4. Orchestrate agent teams for maximum efficiency and success
Agent organization checklist: Agent organization checklist:
- Agent selection accuracy > 95% achieved - Agent selection accuracy > 95% achieved
- Task completion rate > 99% maintained - Task completion rate > 99% maintained
- Resource utilization optimal consistently - Resource utilization optimal consistently
@@ -24,6 +26,7 @@ Agent organization checklist:
- Team synergy maximized effectively - Team synergy maximized effectively
Task decomposition: Task decomposition:
- Requirement analysis - Requirement analysis
- Subtask identification - Subtask identification
- Dependency mapping - Dependency mapping
@@ -34,6 +37,7 @@ Task decomposition:
- Success criteria - Success criteria
Agent capability mapping: Agent capability mapping:
- Skill inventory - Skill inventory
- Performance metrics - Performance metrics
- Specialization areas - Specialization areas
@@ -44,6 +48,7 @@ Agent capability mapping:
- Workload capacity - Workload capacity
Team assembly: Team assembly:
- Optimal composition - Optimal composition
- Skill coverage - Skill coverage
- Role assignment - Role assignment
@@ -54,6 +59,7 @@ Team assembly:
- Timeline synchronization - Timeline synchronization
Orchestration patterns: Orchestration patterns:
- Sequential execution - Sequential execution
- Parallel processing - Parallel processing
- Pipeline patterns - Pipeline patterns
@@ -64,6 +70,7 @@ Orchestration patterns:
- Failover strategies - Failover strategies
Workflow design: Workflow design:
- Process modeling - Process modeling
- Data flow planning - Data flow planning
- Control flow design - Control flow design
@@ -74,6 +81,7 @@ Workflow design:
- Result aggregation - Result aggregation
Agent selection criteria: Agent selection criteria:
- Capability matching - Capability matching
- Performance history - Performance history
- Cost considerations - Cost considerations
@@ -84,6 +92,7 @@ Agent selection criteria:
- Backup selection - Backup selection
Dependency management: Dependency management:
- Task dependencies - Task dependencies
- Resource dependencies - Resource dependencies
- Data dependencies - Data dependencies
@@ -94,6 +103,7 @@ Dependency management:
- Flow optimization - Flow optimization
Performance optimization: Performance optimization:
- Bottleneck identification - Bottleneck identification
- Load distribution - Load distribution
- Parallel execution - Parallel execution
@@ -104,6 +114,7 @@ Performance optimization:
- Cost minimization - Cost minimization
Team dynamics: Team dynamics:
- Optimal team size - Optimal team size
- Skill complementarity - Skill complementarity
- Communication overhead - Communication overhead
@@ -114,6 +125,7 @@ Team dynamics:
- Result integration - Result integration
Monitoring & adaptation: Monitoring & adaptation:
- Real-time tracking - Real-time tracking
- Performance metrics - Performance metrics
- Anomaly detection - Anomaly detection
@@ -130,6 +142,7 @@ Monitoring & adaptation:
Initialize agent organization by understanding task and team requirements. Initialize agent organization by understanding task and team requirements.
Organization context query: Organization context query:
```json ```json
{ {
"requesting_agent": "agent-organizer", "requesting_agent": "agent-organizer",
@@ -149,6 +162,7 @@ Execute agent organization through systematic phases:
Decompose and understand task requirements. Decompose and understand task requirements.
Analysis priorities: Analysis priorities:
- Task breakdown - Task breakdown
- Complexity assessment - Complexity assessment
- Dependency identification - Dependency identification
@@ -159,6 +173,7 @@ Analysis priorities:
- Quality standards - Quality standards
Task evaluation: Task evaluation:
- Parse requirements - Parse requirements
- Identify subtasks - Identify subtasks
- Map dependencies - Map dependencies
@@ -173,6 +188,7 @@ Task evaluation:
Assemble and coordinate agent teams. Assemble and coordinate agent teams.
Implementation approach: Implementation approach:
- Select agents - Select agents
- Assign roles - Assign roles
- Setup communication - Setup communication
@@ -183,6 +199,7 @@ Implementation approach:
- Optimize performance - Optimize performance
Organization patterns: Organization patterns:
- Capability-based selection - Capability-based selection
- Load-balanced assignment - Load-balanced assignment
- Redundant coverage - Redundant coverage
@@ -193,6 +210,7 @@ Organization patterns:
- Result validation - Result validation
Progress tracking: Progress tracking:
```json ```json
{ {
"agent": "agent-organizer", "agent": "agent-organizer",
@@ -211,6 +229,7 @@ Progress tracking:
Achieve optimal multi-agent coordination. Achieve optimal multi-agent coordination.
Excellence checklist: Excellence checklist:
- Tasks completed - Tasks completed
- Performance optimal - Performance optimal
- Resources efficient - Resources efficient
@@ -224,6 +243,7 @@ Delivery notification:
"Agent orchestration completed. Coordinated 12 agents across 47 tasks with 94% first-pass success rate. Average response time 3.2s with 67% resource utilization. Achieved 23% performance improvement through optimal team composition and workflow design." "Agent orchestration completed. Coordinated 12 agents across 47 tasks with 94% first-pass success rate. Average response time 3.2s with 67% resource utilization. Achieved 23% performance improvement through optimal team composition and workflow design."
Team composition strategies: Team composition strategies:
- Skill diversity - Skill diversity
- Redundancy planning - Redundancy planning
- Communication efficiency - Communication efficiency
@@ -234,6 +254,7 @@ Team composition strategies:
- Scalability design - Scalability design
Workflow optimization: Workflow optimization:
- Parallel execution - Parallel execution
- Pipeline efficiency - Pipeline efficiency
- Resource sharing - Resource sharing
@@ -244,6 +265,7 @@ Workflow optimization:
- Result synthesis - Result synthesis
Dynamic adaptation: Dynamic adaptation:
- Performance monitoring - Performance monitoring
- Bottleneck detection - Bottleneck detection
- Agent reallocation - Agent reallocation
@@ -254,6 +276,7 @@ Dynamic adaptation:
- Resource scaling - Resource scaling
Coordination excellence: Coordination excellence:
- Clear communication - Clear communication
- Efficient handoffs - Efficient handoffs
- Synchronized execution - Synchronized execution
@@ -264,6 +287,7 @@ Coordination excellence:
- Continuous improvement - Continuous improvement
Learning & improvement: Learning & improvement:
- Performance analysis - Performance analysis
- Pattern recognition - Pattern recognition
- Best practice extraction - Best practice extraction
@@ -274,6 +298,7 @@ Learning & improvement:
- Knowledge base update - Knowledge base update
Integration with other agents: Integration with other agents:
- Collaborate with context-manager on information sharing - Collaborate with context-manager on information sharing
- Support multi-agent-coordinator on execution - Support multi-agent-coordinator on execution
- Work with task-distributor on load balancing - Work with task-distributor on load balancing
+16
View File
@@ -40,6 +40,7 @@ owners:
``` ```
**How to get the repositoryID:** **How to get the repositoryID:**
1. Log into artifacthub.io 1. Log into artifacthub.io
2. Go to Control Panel → Repositories → Add 2. Go to Control Panel → Repositories → Add
3. Select repository kind: "Headlamp plugins" 3. Select repository kind: "Headlamp plugins"
@@ -99,6 +100,7 @@ annotations: # CRITICAL — Headlamp-specific
These annotations in `artifacthub-pkg.yml` are what make ArtifactHub treat the package as a Headlamp plugin: These annotations in `artifacthub-pkg.yml` are what make ArtifactHub treat the package as a Headlamp plugin:
### headlamp/plugin/archive-url ### headlamp/plugin/archive-url
**Required.** Direct download URL to the plugin tarball on GitHub Releases. **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` Format: `https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname>-<VERSION>.tar.gz`
@@ -108,6 +110,7 @@ Format: `https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname
- The tarball is uploaded as a GitHub Release asset — NOT to ArtifactHub - The tarball is uploaded as a GitHub Release asset — NOT to ArtifactHub
### headlamp/plugin/archive-checksum ### headlamp/plugin/archive-checksum
**Recommended.** SHA256 checksum of the tarball. **Recommended.** SHA256 checksum of the tarball.
Format: `sha256:<hex-digest>` Format: `sha256:<hex-digest>`
@@ -117,14 +120,17 @@ Generated via: `sha256sum <tarball> | awk '{print $1}'`
Can be empty string if not yet computed (release workflow fills it in). Can be empty string if not yet computed (release workflow fills it in).
### headlamp/plugin/version-compat ### headlamp/plugin/version-compat
**Required.** Minimum Headlamp version the plugin works with. **Required.** Minimum Headlamp version the plugin works with.
Format: `>=X.Y.Z` (e.g., `>=0.20.0`, `>=0.26`) Format: `>=X.Y.Z` (e.g., `>=0.20.0`, `>=0.26`)
### headlamp/plugin/distro-compat ### headlamp/plugin/distro-compat
**Required.** Comma-separated list of supported Headlamp deployment targets. **Required.** Comma-separated list of supported Headlamp deployment targets.
Valid values: Valid values:
- `in-cluster` — Headlamp running inside a Kubernetes cluster - `in-cluster` — Headlamp running inside a Kubernetes cluster
- `web` — Web-based Headlamp deployment - `web` — Web-based Headlamp deployment
- `app` — Headlamp desktop application (Electron) - `app` — Headlamp desktop application (Electron)
@@ -138,6 +144,7 @@ Example: `"in-cluster,web,app"`
## ArtifactHub Categories ## ArtifactHub Categories
Valid `category` values for Headlamp plugins: Valid `category` values for Headlamp plugins:
- `security` — Secrets, RBAC, policy enforcement - `security` — Secrets, RBAC, policy enforcement
- `storage` — CSI drivers, persistent volumes, Ceph/Rook - `storage` — CSI drivers, persistent volumes, Ceph/Rook
- `monitoring-logging` — Metrics, GPU monitoring, observability - `monitoring-logging` — Metrics, GPU monitoring, observability
@@ -148,7 +155,9 @@ Valid `category` values for Headlamp plugins:
## Optional Fields ## Optional Fields
### containersImages ### containersImages
For plugins associated with a specific container/operator: For plugins associated with a specific container/operator:
```yaml ```yaml
containersImages: containersImages:
- name: <component-name> - name: <component-name>
@@ -156,14 +165,18 @@ containersImages:
``` ```
### recommendations ### recommendations
Link to related ArtifactHub packages: Link to related ArtifactHub packages:
```yaml ```yaml
recommendations: recommendations:
- url: https://artifacthub.io/packages/helm/<repo>/<chart> - url: https://artifacthub.io/packages/helm/<repo>/<chart>
``` ```
### install ### install
Custom installation instructions (markdown): Custom installation instructions (markdown):
```yaml ```yaml
install: | install: |
## Install via Headlamp Plugin Manager ## Install via Headlamp Plugin Manager
@@ -171,6 +184,7 @@ install: |
``` ```
### logoPath ### logoPath
Path to a logo image file in the repo (relative to root). Path to a logo image file in the repo (relative to root).
--- ---
@@ -196,6 +210,7 @@ This is the actual flow. There is NO other way to publish:
``` ```
**Key points:** **Key points:**
- Steps 1-9 happen in your GitHub Actions workflow - Steps 1-9 happen in your GitHub Actions workflow
- Step 10 is entirely controlled by ArtifactHub — you cannot trigger it - Step 10 is entirely controlled by ArtifactHub — you cannot trigger it
- The tarball lives on GitHub Releases, not ArtifactHub - The tarball lives on GitHub Releases, not ArtifactHub
@@ -233,6 +248,7 @@ The `<pkgname>` directory inside the tarball matches the `name` field from `pack
## Validating Metadata ## Validating Metadata
Before committing, check: Before committing, check:
1. `version` matches across `package.json` and `artifacthub-pkg.yml` 1. `version` matches across `package.json` and `artifacthub-pkg.yml`
2. `archive-url` version tag matches the `version` field 2. `archive-url` version tag matches the `version` field
3. `name` in `artifacthub-pkg.yml` matches `package.json` `name` 3. `name` in `artifacthub-pkg.yml` matches `package.json` `name`
@@ -99,6 +99,7 @@ class KubeObject<T extends KubeObjectInterface> {
### ResourceClasses ### ResourceClasses
All standard K8s resource types available (Secret, Namespace, Pod, etc.): All standard K8s resource types available (Secret, Namespace, Pod, etc.):
```typescript ```typescript
const [secrets, error, loading] = K8s.ResourceClasses.Secret.useList({ namespace: 'default' }); const [secrets, error, loading] = K8s.ResourceClasses.Secret.useList({ namespace: 'default' });
const [secret, error] = K8s.ResourceClasses.Secret.useGet('my-secret', 'default'); const [secret, error] = K8s.ResourceClasses.Secret.useGet('my-secret', 'default');
@@ -127,6 +128,7 @@ ApiProxy.apiFactory(group, version, resource): ApiClient
``` ```
**Service proxy URL** (accessing in-cluster services): **Service proxy URL** (accessing in-cluster services):
``` ```
/api/v1/namespaces/${ns}/services/http:${name}:${port}/proxy${path} /api/v1/namespaces/${ns}/services/http:${name}:${port}/proxy${path}
``` ```
@@ -146,6 +148,7 @@ From `@kinvolk/headlamp-plugin/lib/CommonComponents`:
`PercentageBar` — bar chart with `data` array of `{ name, value, fill }` `PercentageBar` — bar chart with `data` array of `{ name, value, fill }`
### SimpleTable (non-obvious props) ### SimpleTable (non-obvious props)
```typescript ```typescript
<SimpleTable <SimpleTable
data={items} data={items}
@@ -158,6 +161,7 @@ From `@kinvolk/headlamp-plugin/lib/CommonComponents`:
``` ```
### NameValueTable (non-obvious props) ### NameValueTable (non-obvious props)
```typescript ```typescript
<NameValueTable <NameValueTable
rows={[ rows={[
@@ -168,6 +172,7 @@ From `@kinvolk/headlamp-plugin/lib/CommonComponents`:
``` ```
### ConfigStore ### ConfigStore
```typescript ```typescript
import { ConfigStore } from '@kinvolk/headlamp-plugin/lib'; import { ConfigStore } from '@kinvolk/headlamp-plugin/lib';
const store = new ConfigStore<MyConfig>('plugin-name'); const store = new ConfigStore<MyConfig>('plugin-name');
@@ -177,6 +182,7 @@ store.useConfig(): () => MyConfig;
``` ```
### Pre-bundled (no package.json entry needed) ### Pre-bundled (no package.json entry needed)
react, react-dom, react-router-dom, @iconify/react, react-redux, @material-ui/core, @material-ui/styles, lodash, notistack, recharts, monaco-editor react, react-dom, react-router-dom, @iconify/react, react-redux, @material-ui/core, @material-ui/styles, lodash, notistack, recharts, monaco-editor
--- ---
@@ -264,6 +270,7 @@ vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
Headlamp supports light and dark themes. **Never hardcode colors.** Use CSS custom properties with light-mode fallbacks: Headlamp supports light and dark themes. **Never hardcode colors.** Use CSS custom properties with light-mode fallbacks:
### Required CSS variables for inline styles ### Required CSS variables for inline styles
```typescript ```typescript
// Text // Text
color: 'var(--mui-palette-text-primary)' color: 'var(--mui-palette-text-primary)'
@@ -289,6 +296,7 @@ color: 'var(--link-color, #1976d2)'
``` ```
### Common mistakes to avoid ### Common mistakes to avoid
- **NEVER** use raw `#fff`, `#000`, `#333`, `#666` etc. without wrapping in `var(--mui-palette-*)` - **NEVER** use raw `#fff`, `#000`, `#333`, `#666` etc. without wrapping in `var(--mui-palette-*)`
- **NEVER** use `rgba(0,0,0,0.5)` for overlays without a variable — this is the one exception where raw rgba is acceptable (backdrop overlays) - **NEVER** use `rgba(0,0,0,0.5)` for overlays without a variable — this is the one exception where raw rgba is acceptable (backdrop overlays)
- **NEVER** assume white backgrounds or dark text — always use `background-paper`/`text-primary` - **NEVER** assume white backgrounds or dark text — always use `background-paper`/`text-primary`
@@ -296,6 +304,7 @@ color: 'var(--link-color, #1976d2)'
- Fallback values after the comma are for environments where the variable isn't set — always use the light-mode default - Fallback values after the comma are for environments where the variable isn't set — always use the light-mode default
### Form inputs in custom components ### Form inputs in custom components
```typescript ```typescript
const inputStyle = { const inputStyle = {
border: '1px solid var(--mui-palette-divider, #ccc)', border: '1px solid var(--mui-palette-divider, #ccc)',
+25
View File
@@ -8,12 +8,14 @@ model: opus
You are a senior multi-agent coordinator with expertise in orchestrating complex distributed workflows. Your focus spans inter-agent communication, task dependency management, parallel execution control, and fault tolerance with emphasis on ensuring efficient, reliable coordination across large agent teams. You are a senior multi-agent coordinator with expertise in orchestrating complex distributed workflows. Your focus spans inter-agent communication, task dependency management, parallel execution control, and fault tolerance with emphasis on ensuring efficient, reliable coordination across large agent teams.
When invoked: When invoked:
1. Query context manager for workflow requirements and agent states 1. Query context manager for workflow requirements and agent states
2. Review communication patterns, dependencies, and resource constraints 2. Review communication patterns, dependencies, and resource constraints
3. Analyze coordination bottlenecks, deadlock risks, and optimization opportunities 3. Analyze coordination bottlenecks, deadlock risks, and optimization opportunities
4. Implement robust multi-agent coordination strategies 4. Implement robust multi-agent coordination strategies
Multi-agent coordination checklist: Multi-agent coordination checklist:
- Coordination overhead < 5% maintained - Coordination overhead < 5% maintained
- Deadlock prevention 100% ensured - Deadlock prevention 100% ensured
- Message delivery guaranteed thoroughly - Message delivery guaranteed thoroughly
@@ -24,6 +26,7 @@ Multi-agent coordination checklist:
- Performance optimal consistently - Performance optimal consistently
Workflow orchestration: Workflow orchestration:
- Process design - Process design
- Flow control - Flow control
- State management - State management
@@ -34,6 +37,7 @@ Workflow orchestration:
- Result aggregation - Result aggregation
Inter-agent communication: Inter-agent communication:
- Protocol design - Protocol design
- Message routing - Message routing
- Channel management - Channel management
@@ -44,6 +48,7 @@ Inter-agent communication:
- Backpressure handling - Backpressure handling
Dependency management: Dependency management:
- Dependency graphs - Dependency graphs
- Topological sorting - Topological sorting
- Circular detection - Circular detection
@@ -54,6 +59,7 @@ Dependency management:
- Race condition handling - Race condition handling
Coordination patterns: Coordination patterns:
- Master-worker - Master-worker
- Peer-to-peer - Peer-to-peer
- Hierarchical - Hierarchical
@@ -64,6 +70,7 @@ Coordination patterns:
- Consensus-based - Consensus-based
Parallel execution: Parallel execution:
- Task partitioning - Task partitioning
- Work distribution - Work distribution
- Load balancing - Load balancing
@@ -74,6 +81,7 @@ Parallel execution:
- Result merging - Result merging
Communication mechanisms: Communication mechanisms:
- Message passing - Message passing
- Shared memory - Shared memory
- Event streams - Event streams
@@ -84,6 +92,7 @@ Communication mechanisms:
- Queue systems - Queue systems
Resource coordination: Resource coordination:
- Resource allocation - Resource allocation
- Lock management - Lock management
- Semaphore control - Semaphore control
@@ -94,6 +103,7 @@ Resource coordination:
- Efficiency optimization - Efficiency optimization
Fault tolerance: Fault tolerance:
- Failure detection - Failure detection
- Timeout handling - Timeout handling
- Retry mechanisms - Retry mechanisms
@@ -104,6 +114,7 @@ Fault tolerance:
- Graceful degradation - Graceful degradation
Workflow management: Workflow management:
- DAG execution - DAG execution
- State machines - State machines
- Saga patterns - Saga patterns
@@ -114,6 +125,7 @@ Workflow management:
- Loop handling - Loop handling
Performance optimization: Performance optimization:
- Bottleneck analysis - Bottleneck analysis
- Pipeline optimization - Pipeline optimization
- Batch processing - Batch processing
@@ -130,6 +142,7 @@ Performance optimization:
Initialize multi-agent coordination by understanding workflow needs. Initialize multi-agent coordination by understanding workflow needs.
Coordination context query: Coordination context query:
```json ```json
{ {
"requesting_agent": "multi-agent-coordinator", "requesting_agent": "multi-agent-coordinator",
@@ -149,6 +162,7 @@ Execute multi-agent coordination through systematic phases:
Design efficient coordination strategies. Design efficient coordination strategies.
Analysis priorities: Analysis priorities:
- Workflow mapping - Workflow mapping
- Agent capabilities - Agent capabilities
- Communication needs - Communication needs
@@ -159,6 +173,7 @@ Analysis priorities:
- Optimization opportunities - Optimization opportunities
Workflow evaluation: Workflow evaluation:
- Map processes - Map processes
- Identify dependencies - Identify dependencies
- Analyze communication - Analyze communication
@@ -173,6 +188,7 @@ Workflow evaluation:
Orchestrate complex multi-agent workflows. Orchestrate complex multi-agent workflows.
Implementation approach: Implementation approach:
- Setup communication - Setup communication
- Configure workflows - Configure workflows
- Manage dependencies - Manage dependencies
@@ -183,6 +199,7 @@ Implementation approach:
- Optimize performance - Optimize performance
Coordination patterns: Coordination patterns:
- Efficient messaging - Efficient messaging
- Clear dependencies - Clear dependencies
- Parallel execution - Parallel execution
@@ -193,6 +210,7 @@ Coordination patterns:
- Continuous optimization - Continuous optimization
Progress tracking: Progress tracking:
```json ```json
{ {
"agent": "multi-agent-coordinator", "agent": "multi-agent-coordinator",
@@ -211,6 +229,7 @@ Progress tracking:
Achieve seamless multi-agent collaboration. Achieve seamless multi-agent collaboration.
Excellence checklist: Excellence checklist:
- Workflows smooth - Workflows smooth
- Communication efficient - Communication efficient
- Dependencies resolved - Dependencies resolved
@@ -224,6 +243,7 @@ Delivery notification:
"Multi-agent coordination completed. Orchestrated 87 agents processing 234K messages/minute with 94% workflow completion rate. Achieved 96% coordination efficiency with zero deadlocks and 99.9% message delivery guarantee." "Multi-agent coordination completed. Orchestrated 87 agents processing 234K messages/minute with 94% workflow completion rate. Achieved 96% coordination efficiency with zero deadlocks and 99.9% message delivery guarantee."
Communication optimization: Communication optimization:
- Protocol efficiency - Protocol efficiency
- Message batching - Message batching
- Compression strategies - Compression strategies
@@ -234,6 +254,7 @@ Communication optimization:
- Queue management - Queue management
Dependency resolution: Dependency resolution:
- Graph algorithms - Graph algorithms
- Priority scheduling - Priority scheduling
- Resource allocation - Resource allocation
@@ -244,6 +265,7 @@ Dependency resolution:
- Bottleneck removal - Bottleneck removal
Fault handling: Fault handling:
- Failure detection - Failure detection
- Isolation strategies - Isolation strategies
- Recovery procedures - Recovery procedures
@@ -254,6 +276,7 @@ Fault handling:
- Graceful degradation - Graceful degradation
Scalability patterns: Scalability patterns:
- Horizontal scaling - Horizontal scaling
- Vertical partitioning - Vertical partitioning
- Load distribution - Load distribution
@@ -264,6 +287,7 @@ Scalability patterns:
- Cluster coordination - Cluster coordination
Performance tuning: Performance tuning:
- Latency analysis - Latency analysis
- Throughput optimization - Throughput optimization
- Resource utilization - Resource utilization
@@ -274,6 +298,7 @@ Performance tuning:
- I/O optimization - I/O optimization
Integration with other agents: Integration with other agents:
- Collaborate with agent-organizer on team assembly - Collaborate with agent-organizer on team assembly
- Support context-manager on state synchronization - Support context-manager on state synchronization
- Work with workflow-orchestrator on process execution - Work with workflow-orchestrator on process execution
+20
View File
@@ -0,0 +1,20 @@
name: Dual Approval (CTO + QA)
# Calls the shared dual-approval-check workflow.
# Passes when both privilegedescalation-cto and privilegedescalation-qa
# have approved the PR. Add "Dual Approval (CTO + QA)" to required_status_checks
# in branch protection to enforce this gate.
on:
pull_request_review:
types: [submitted, dismissed]
pull_request:
branches: [main]
types: [opened, reopened, synchronize]
jobs:
dual-approval:
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
secrets: inherit
with:
pr_number: ${{ github.event.pull_request.number }}
+48 -100
View File
@@ -7,13 +7,29 @@ on:
branches: [main] branches: [main]
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
# Only one E2E run at a time: the shared E2E_RELEASE (headlamp-e2e) in
# privilegedescalation-dev cannot be shared across concurrent runs.
# cancel-in-progress: false (queue, don't cancel) — cancelling in-flight
# runs may skip the if: always() teardown, leaving dangling cluster resources.
concurrency:
group: e2e-${{ github.repository }}
cancel-in-progress: false
env: env:
HEADLAMP_NAMESPACE: kube-system E2E_NAMESPACE: privilegedescalation-dev
HEADLAMP_DEPLOY: headlamp E2E_RELEASE: headlamp-e2e
# Pin to a known-good Headlamp version. Using :latest is risky because
# the tag can change between CI runs, causing flaky failures when a newer
# image is pulled on some nodes but not others (IfNotPresent pull policy).
# Update this when Headlamp is upgraded in production (kube-system).
HEADLAMP_VERSION: v0.40.1
jobs: jobs:
e2e: e2e:
runs-on: local-ubuntu-latest runs-on: runners-privilegedescalation
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
@@ -21,125 +37,57 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Build plugin - name: Build plugin
run: npm run build run: npx @kinvolk/headlamp-plugin build
- name: Setup kubectl - name: Deploy E2E Headlamp instance
uses: azure/setup-kubectl@v4 run: scripts/deploy-e2e-headlamp.sh
- name: Ensure PVC exists - name: Load E2E environment
run: kubectl apply -f deployment/headlamp-plugins-pvc.yaml
- name: Patch Headlamp deployment with shared volume mount
run: | run: |
NS="$HEADLAMP_NAMESPACE" if [ -f .env.e2e ]; then
DEPLOY="$HEADLAMP_DEPLOY" cat .env.e2e >> "$GITHUB_ENV"
# Check if the plugins volume and mount already exist (by name or mountPath)
DEPLOY_JSON=$(kubectl get deploy "$DEPLOY" -n "$NS" -o json)
HAS_VOL=$(echo "$DEPLOY_JSON" | \
python3 -c "import sys,json; d=json.load(sys.stdin); vols=d['spec']['template']['spec'].get('volumes',[]); print('yes' if any(v.get('persistentVolumeClaim',{}).get('claimName')=='headlamp-plugins' or v.get('name')=='plugins' for v in vols) else '')")
HAS_MOUNT=$(echo "$DEPLOY_JSON" | \
python3 -c "import sys,json; d=json.load(sys.stdin); mounts=d['spec']['template']['spec']['containers'][0].get('volumeMounts',[]); print('yes' if any(m.get('mountPath')=='/headlamp/plugins' or m.get('name')=='plugins' for m in mounts) else '')")
NEEDS_PATCH=false
if [ -z "$HAS_VOL" ]; then
echo "Adding plugins PVC volume..."
kubectl patch deploy "$DEPLOY" -n "$NS" --type=json -p '[
{"op":"add","path":"/spec/template/spec/volumes/-","value":{
"name":"plugins",
"persistentVolumeClaim":{"claimName":"headlamp-plugins"}
}}
]'
NEEDS_PATCH=true
else else
echo "Plugins volume already present, skipping." echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
fi
if [ -z "$HAS_MOUNT" ]; then
echo "Adding plugins volume mount..."
kubectl patch deploy "$DEPLOY" -n "$NS" --type=json -p '[
{"op":"add","path":"/spec/template/spec/containers/0/volumeMounts/-","value":{
"name":"plugins",
"mountPath":"/headlamp/plugins",
"readOnly":true
}}
]'
NEEDS_PATCH=true
else
echo "Plugins volume mount already present, skipping."
fi
# Set the plugins directory via env var
kubectl set env deploy/"$DEPLOY" -n "$NS" \
HEADLAMP_CONFIG_PLUGIN_DIR=/headlamp/plugins
# Wait for rollout
kubectl rollout status deploy/"$DEPLOY" -n "$NS" --timeout=120s
- name: Deploy plugin via shared volume
run: scripts/deploy-plugin-via-volume.sh
- name: Preflight — verify Headlamp and plugin availability
env:
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
run: |
PLUGIN_NAME=$(node -p "require('./package.json').name")
EXPECTED=$(node -p "require('./package.json').version")
echo "Expecting: $PLUGIN_NAME@$EXPECTED"
# Wait for Headlamp to be reachable
for i in $(seq 1 30); do
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 "$HEADLAMP_URL" || true)
if [ "$HTTP_CODE" != "000" ]; then
echo "Headlamp responded HTTP $HTTP_CODE"
break
fi
echo "Waiting for Headlamp... ($i/30)"
sleep 2
done
if [ "$HTTP_CODE" = "000" ]; then
echo "::error::Cannot reach Headlamp at $HEADLAMP_URL after 60s"
exit 1 exit 1
fi fi
# Verify plugin is visible
PLUGIN_JSON=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
node -e "
const plugins = JSON.parse(process.argv[1]);
console.log('Installed plugins:');
for (const p of plugins) console.log(' ' + p.name + '@' + (p.version||'unknown'));
const ours = plugins.find(p => p.name === '$PLUGIN_NAME' || p.name === 'polaris' || p.name.includes('polaris'));
if (!ours) {
console.log('::warning::Plugin $PLUGIN_NAME not yet visible — Headlamp may need a restart');
} else {
console.log('Found plugin: ' + ours.name + ' at path ' + ours.path);
}
" "$PLUGIN_JSON"
- name: Install Playwright browsers - name: Install Playwright browsers
run: npx playwright install --with-deps chromium run: npx playwright install --with-deps chromium
- name: Run E2E tests - name: Run E2E tests
run: npm run e2e run: npm run e2e
env: env:
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }} HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }} HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }} - name: Collect deployment diagnostics on failure
if: failure()
run: |
echo "=== Pod state ==="
kubectl get pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Pod describe ==="
kubectl describe pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Recent namespace events ==="
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -20 || true
- name: Teardown E2E instance
if: always()
run: scripts/teardown-e2e-headlamp.sh
- name: Upload Playwright report - name: Upload Playwright report
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
if: failure() if: failure()
with: with:
name: playwright-report name: playwright-report
@@ -147,7 +95,7 @@ jobs:
retention-days: 7 retention-days: 7
- name: Upload test results - name: Upload test results
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
if: failure() if: failure()
with: with:
name: test-results name: test-results
+3
View File
@@ -15,6 +15,9 @@ permissions:
jobs: jobs:
release: release:
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main 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: with:
version: ${{ inputs.version }} version: ${{ inputs.version }}
upstream-repo: 'FairwindsOps/polaris' upstream-repo: 'FairwindsOps/polaris'
+1
View File
@@ -6,5 +6,6 @@ e2e/.auth/
test-results/ test-results/
.playwright-mcp/ .playwright-mcp/
.env .env
.env.e2e
.env.local .env.local
.eslintcache .eslintcache
+20
View File
@@ -0,0 +1,20 @@
{
"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
}
}
+76 -1
View File
@@ -7,15 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.0.0] - 2026-03-22
First stable release. The plugin API (routes, sidebar entries, settings schema, and app bar action) is
now frozen — no breaking changes without a new major version.
### Security
- Patched 8 of 9 npm audit vulnerabilities via `pnpm.overrides` (#92)
### Added
- **Dual-approval CI check**: PRs now require approval from both CTO and QA before merging (#98, #76)
- **ExemptionManager test suite**: Full coverage of annotation-based exemption flows, exemption creation, and inline feedback (#82)
- **RBAC preflight check**: `deploy-e2e-headlamp.sh` now verifies runner RBAC before attempting E2E deploy (#80)
### Fixed
- **E2E infrastructure overhaul**: Replaced Dockerfile.e2e with ConfigMap volume mount for plugin loading; tests now run in the `privilegedescalation-dev` namespace (#73, #89, #94)
- **E2E token auth**: Workflow uses GitHub App token auth and handles the `/token` redirect correctly (#97)
- **E2E HTTP readiness**: `deploy-e2e-headlamp.sh` waits for HTTP reachability after rollout before running tests (#104)
- **E2E runner label**: Updated to `runners-privilegedescalation` for self-hosted ARC runners (#71)
- **Direct devDependencies**: Added `typescript`, `eslint`, `prettier`, and `@headlamp-k8s/eslint-config` as explicit direct devDependencies to prevent phantom-dep failures in clean installs (#95, #102)
### Changed
- **pnpm version pinned**: `packageManager` field in `package.json` pins the pnpm version used in CI (#103)
- **GitHub Actions SHA pinning**: Renovate `pinDigests` enabled to SHA-pin all GitHub Actions (#105)
- **ArtifactHub metadata polish**: Improved `install` instructions and `changes` section formatting (#82)
## [0.6.0] - 2026-03-04 ## [0.6.0] - 2026-03-04
### Fixed ### Fixed
- **ExemptionManager apiVersion bug**: `apps` and `batch` resources now correctly use `/apis/{group}/v1/` instead of the broken `/api/v1/` path - **ExemptionManager apiVersion bug**: `apps` and `batch` resources now correctly use `/apis/{group}/v1/` instead of the broken `/api/v1/` path
- **Strict TypeScript**: Replaced `resource: any` in InlineAuditSection with proper `KubeResource` interface - **Strict TypeScript**: Replaced `resource: any` in InlineAuditSection with proper `KubeResource` interface
- **PolarisDataContext test mock**: Added missing `triggerRefresh` to mock, preventing silent `undefined` for `refresh` in context - **PolarisDataContext test mock**: Added missing `triggerRefresh` to mock, preventing silent `undefined` for `refresh` in context
- **DashboardView test**: Fixed `SimpleTable` mock that used `Array<any>` and didn't exercise column getters - **DashboardView test**: Fixed `SimpleTable` mock that used `Array<any>` and didn't exercise column getters
### Changed ### Changed
- **Dark mode / theming**: Replaced all `var(--mui-palette-*)` CSS variables with `useTheme()` + `theme.palette.*` across all components (DashboardView, NamespacesListView, InlineAuditSection, ExemptionManager, PolarisSettings, AppBarScoreBadge) - **Dark mode / theming**: Replaced all `var(--mui-palette-*)` CSS variables with `useTheme()` + `theme.palette.*` across all components (DashboardView, NamespacesListView, InlineAuditSection, ExemptionManager, PolarisSettings, AppBarScoreBadge)
- **Namespace drawer**: Replaced custom `<style>` block + positioned `<div>` with MUI `Drawer` component for proper accessibility (`role="dialog"`, `aria-modal`, Escape key handling via MUI) - **Namespace drawer**: Replaced custom `<style>` block + positioned `<div>` with MUI `Drawer` component for proper accessibility (`role="dialog"`, `aria-modal`, Escape key handling via MUI)
- **AppBarScoreBadge**: Uses `theme.palette.success/warning/error` with proper `contrastText` instead of hardcoded hex colors - **AppBarScoreBadge**: Uses `theme.palette.success/warning/error` with proper `contrastText` instead of hardcoded hex colors
@@ -23,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **URL construction**: Exported `getPolarisApiPath` and `isFullUrl` from `polaris.ts`; PolarisSettings now reuses them instead of duplicating logic - **URL construction**: Exported `getPolarisApiPath` and `isFullUrl` from `polaris.ts`; PolarisSettings now reuses them instead of duplicating logic
### Added ### Added
- **Error boundaries**: All registered components (routes, detail sections, app bar action) wrapped in `PolarisErrorBoundary` for graceful error rendering - **Error boundaries**: All registered components (routes, detail sections, app bar action) wrapped in `PolarisErrorBoundary` for graceful error rendering
- **Tests for InlineAuditSection** (7 tests): loading, unsupported kind, not found, score/summary, failing checks, link, exemption manager - **Tests for InlineAuditSection** (7 tests): loading, unsupported kind, not found, score/summary, failing checks, link, exemption manager
- **Tests for AppBarScoreBadge** (6 tests): loading, no data, score colors, navigation, aria-label - **Tests for AppBarScoreBadge** (6 tests): loading, no data, score colors, navigation, aria-label
@@ -30,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Tests for checkMapping.ts** (11 tests): name/description/category/severity lookups, unknown checks, CHECK_MAPPING structure validation - **Tests for checkMapping.ts** (11 tests): name/description/category/severity lookups, unknown checks, CHECK_MAPPING structure validation
### Removed ### Removed
- **NamespaceDetailView.tsx**: Dead code with no registered route (replaced by drawer in NamespacesListView) - **NamespaceDetailView.tsx**: Dead code with no registered route (replaced by drawer in NamespacesListView)
- **NamespaceDetailView.test.tsx**: Tests for removed component - **NamespaceDetailView.test.tsx**: Tests for removed component
- **MockPolarisProvider in test-utils.tsx**: Unused mock provider (tests use `vi.mock` instead) - **MockPolarisProvider in test-utils.tsx**: Unused mock provider (tests use `vi.mock` instead)
@@ -38,9 +71,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.5] - 2026-02-12 ## [0.3.5] - 2026-02-12
### Fixed ### Fixed
- Fixed drawer background remaining white in dark mode by using correct CSS variable (`--mui-palette-background-default`) - Fixed drawer background remaining white in dark mode by using correct CSS variable (`--mui-palette-background-default`)
### Documentation ### Documentation
- Added comprehensive Priority 2 documentation (ARCHITECTURE.md, DEPLOYMENT.md, SECURITY.md) - Added comprehensive Priority 2 documentation (ARCHITECTURE.md, DEPLOYMENT.md, SECURITY.md)
- Added CONTRIBUTING.md with development workflow, branching strategy, and code style guidelines - Added CONTRIBUTING.md with development workflow, branching strategy, and code style guidelines
- Added complete CHANGELOG.md documenting all releases from v0.0.1 to current - Added complete CHANGELOG.md documenting all releases from v0.0.1 to current
@@ -48,17 +83,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.4] - 2026-02-12 ## [0.3.4] - 2026-02-12
### Fixed ### Fixed
- Removed all `@mui/material` and `@mui/icons-material` imports causing plugin load failure - Removed all `@mui/material` and `@mui/icons-material` imports causing plugin load failure
- Fixed plugin settings page registration (changed name from 'polaris' to 'headlamp-polaris-plugin') - Fixed plugin settings page registration (changed name from 'polaris' to 'headlamp-polaris-plugin')
- Added dark mode support using MUI CSS variables for proper theme adaptation - Added dark mode support using MUI CSS variables for proper theme adaptation
- Resolved TypeScript compilation errors in plugin registration calls - Resolved TypeScript compilation errors in plugin registration calls
### Changed ### Changed
- Replaced all MUI components with standard HTML elements and inline styles - Replaced all MUI components with standard HTML elements and inline styles
- Updated `registerDetailsViewSection` and `registerAppBarAction` to match Headlamp plugin API v0.13.0 - Updated `registerDetailsViewSection` and `registerAppBarAction` to match Headlamp plugin API v0.13.0
- App bar badge, settings buttons, and UI elements now use theme-aware CSS variables - App bar badge, settings buttons, and UI elements now use theme-aware CSS variables
### Infrastructure ### Infrastructure
- Added CI workflow for lint, type-check, build, and test - Added CI workflow for lint, type-check, build, and test
- Enhanced E2E testing documentation with comprehensive guides - Enhanced E2E testing documentation with comprehensive guides
- Added documentation-engineer subagent - Added documentation-engineer subagent
@@ -66,24 +104,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.3] - 2026-02-12 ## [0.3.3] - 2026-02-12
### Fixed ### Fixed
- Corrected plugin settings registration name to match package.json - Corrected plugin settings registration name to match package.json
- Added displaySaveButton parameter to settings registration - Added displaySaveButton parameter to settings registration
## [0.3.2] - 2026-02-12 ## [0.3.2] - 2026-02-12
### Fixed ### Fixed
- Removed all MUI dependencies to fix plugin loading in Headlamp v0.39.0+ - Removed all MUI dependencies to fix plugin loading in Headlamp v0.39.0+
- Plugin now loads correctly in sidebar and routes - Plugin now loads correctly in sidebar and routes
## [0.3.1] - 2026-02-12 ## [0.3.1] - 2026-02-12
### Fixed ### Fixed
- TypeScript compilation errors in `registerDetailsViewSection` and `registerAppBarAction` calls - TypeScript compilation errors in `registerDetailsViewSection` and `registerAppBarAction` calls
- Test failures in DashboardView (added missing SimpleTable mock) - Test failures in DashboardView (added missing SimpleTable mock)
## [0.3.0] - 2026-02-11 ## [0.3.0] - 2026-02-11
### Added ### Added
- App bar badge displaying cluster Polaris score - App bar badge displaying cluster Polaris score
- Inline audit sections in resource detail views (Deployment, StatefulSet, DaemonSet, Job, CronJob) - Inline audit sections in resource detail views (Deployment, StatefulSet, DaemonSet, Job, CronJob)
- Exemption management UI (view/add exemptions via annotations) - Exemption management UI (view/add exemptions via annotations)
@@ -92,33 +134,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Namespace drawer navigation with URL hash support - Namespace drawer navigation with URL hash support
### Changed ### Changed
- Migrated namespace detail to right-side drawer panel - Migrated namespace detail to right-side drawer panel
- Improved drawer keyboard navigation (Escape to close) - Improved drawer keyboard navigation (Escape to close)
- Enhanced settings page with connection testing - Enhanced settings page with connection testing
### Fixed ### Fixed
- Empty namespace crash handling - Empty namespace crash handling
- Drawer navigation pattern for better UX - Drawer navigation pattern for better UX
## [0.2.5] - 2025-12-XX ## [0.2.5] - 2025-12-XX
### Fixed ### Fixed
- Improved theming and settings visibility - Improved theming and settings visibility
## [0.2.4] - 2025-12-XX ## [0.2.4] - 2025-12-XX
### Changed ### Changed
- Increased namespace detail panel width to 1000px for better readability - Increased namespace detail panel width to 1000px for better readability
## [0.2.3] - 2025-12-XX ## [0.2.3] - 2025-12-XX
### Added ### Added
- Full URL support for custom Polaris dashboards - Full URL support for custom Polaris dashboards
- Support for external Polaris instances (not just service proxy) - Support for external Polaris instances (not just service proxy)
## [0.2.2] - 2025-12-XX ## [0.2.2] - 2025-12-XX
### Added ### Added
- Configurable Polaris dashboard URL setting - Configurable Polaris dashboard URL setting
- Settings page for plugin configuration - Settings page for plugin configuration
- Refresh interval configuration - Refresh interval configuration
@@ -126,136 +174,161 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.2.1] - 2025-12-XX ## [0.2.1] - 2025-12-XX
### Infrastructure ### Infrastructure
- Migrated to GitHub as primary repository - Migrated to GitHub as primary repository
- Fixed v0.2.0 checksum in ArtifactHub metadata - Fixed v0.2.0 checksum in ArtifactHub metadata
## [0.2.0] - 2025-12-XX ## [0.2.0] - 2025-12-XX
### Added ### Added
- Namespace drawer navigation - Namespace drawer navigation
- URL hash-based routing for namespaces - URL hash-based routing for namespaces
- Keyboard shortcuts (Escape to close drawer) - Keyboard shortcuts (Escape to close drawer)
### Infrastructure ### Infrastructure
- GitHub release automation - GitHub release automation
- Improved CI/CD workflow - Improved CI/CD workflow
## [0.1.7] - 2025-11-XX ## [0.1.7] - 2025-11-XX
### Documentation ### Documentation
- Removed incorrect development installation instructions - Removed incorrect development installation instructions
## [0.1.6] - 2025-11-XX ## [0.1.6] - 2025-11-XX
### Fixed ### Fixed
- Plugin settings display name changed to "Polaris" - Plugin settings display name changed to "Polaris"
### Documentation ### Documentation
- Added tooltip to skipped count explaining limitation - Added tooltip to skipped count explaining limitation
- Documented skipped count limitation in README - Documented skipped count limitation in README
## [0.1.5] - 2025-11-XX ## [0.1.5] - 2025-11-XX
### Fixed ### Fixed
- Restored `:80` port in service proxy URL for correct dashboard access - Restored `:80` port in service proxy URL for correct dashboard access
## [0.1.4] - 2025-11-XX ## [0.1.4] - 2025-11-XX
### Added ### Added
- Playwright E2E smoke tests - Playwright E2E smoke tests
- Test coverage for sidebar, overview, namespaces, and detail views - Test coverage for sidebar, overview, namespaces, and detail views
### Fixed ### Fixed
- Empty namespace crash (graceful handling) - Empty namespace crash (graceful handling)
- Removed `:80` port suffix from service proxy URL for RBAC compatibility - Removed `:80` port suffix from service proxy URL for RBAC compatibility
## [0.1.3] - 2025-11-XX ## [0.1.3] - 2025-11-XX
### Fixed ### Fixed
- Service proxy URL format for consistent RBAC requirements - Service proxy URL format for consistent RBAC requirements
## [0.1.2] - 2025-11-XX ## [0.1.2] - 2025-11-XX
### Added ### Added
- Namespace filtering and sorting - Namespace filtering and sorting
- Enhanced resource table in namespace detail view - Enhanced resource table in namespace detail view
## [0.1.1] - 2025-11-XX ## [0.1.1] - 2025-11-XX
### Fixed ### Fixed
- Score calculation for resources with mixed results - Score calculation for resources with mixed results
- Percentage display formatting - Percentage display formatting
## [0.1.0] - 2025-11-XX ## [0.1.0] - 2025-11-XX
### Added ### Added
- Namespace detail view with resource-level audit results - Namespace detail view with resource-level audit results
- Drill-down navigation from namespace list - Drill-down navigation from namespace list
### Changed ### Changed
- Improved data fetching with error handling - Improved data fetching with error handling
- Better loading states - Better loading states
## [0.0.10] - 2025-11-XX ## [0.0.10] - 2025-11-XX
### Fixed ### Fixed
- **RBAC Documentation:** Corrected to use `services/proxy` permission instead of ConfigMap access - **RBAC Documentation:** Corrected to use `services/proxy` permission instead of ConfigMap access
### Documentation ### Documentation
- Updated README with accurate RBAC requirements - Updated README with accurate RBAC requirements
- Added minimal Role example - Added minimal Role example
## [0.0.9] - 2025-11-XX ## [0.0.9] - 2025-11-XX
### Added ### Added
- Refresh button for manual data reload - Refresh button for manual data reload
- Last updated timestamp display - Last updated timestamp display
## [0.0.8] - 2025-11-XX ## [0.0.8] - 2025-11-XX
### Added ### Added
- Skipped checks display in check summary - Skipped checks display in check summary
- Improved check categorization (pass/warning/danger/skipped) - Improved check categorization (pass/warning/danger/skipped)
## [0.0.7] - 2025-11-XX ## [0.0.7] - 2025-11-XX
### Changed ### Changed
- Enhanced overview dashboard layout - Enhanced overview dashboard layout
- Better visual hierarchy for cluster score - Better visual hierarchy for cluster score
## [0.0.6] - 2025-11-XX ## [0.0.6] - 2025-11-XX
### Added ### Added
- Namespace list view with per-namespace scores - Namespace list view with per-namespace scores
- Navigation between overview and namespace views - Navigation between overview and namespace views
## [0.0.5] - 2025-11-XX ## [0.0.5] - 2025-11-XX
### Fixed ### Fixed
- Data fetching error handling - Data fetching error handling
- API proxy path configuration - API proxy path configuration
## [0.0.4] - 2025-11-XX ## [0.0.4] - 2025-11-XX
### Added ### Added
- Check distribution visualization - Check distribution visualization
- Pass/Warning/Danger count display - Pass/Warning/Danger count display
## [0.0.3] - 2025-11-XX ## [0.0.3] - 2025-11-XX
### Changed ### Changed
- Improved cluster score calculation - Improved cluster score calculation
- Better result aggregation logic - Better result aggregation logic
## [0.0.2] - 2025-11-XX ## [0.0.2] - 2025-11-XX
### Added ### Added
- Cluster score display - Cluster score display
- Basic check summary table - Basic check summary table
## [0.0.1] - 2025-10-XX ## [0.0.1] - 2025-10-XX
### Added ### Added
- Initial release - Initial release
- Basic Polaris plugin structure - Basic Polaris plugin structure
- Sidebar entry "Polaris" - Sidebar entry "Polaris"
@@ -265,12 +338,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- React components using Headlamp CommonComponents - React components using Headlamp CommonComponents
### Infrastructure ### Infrastructure
- GitHub repository setup - GitHub repository setup
- ArtifactHub package registration - ArtifactHub package registration
- Automated release workflow - Automated release workflow
- Basic CI/CD pipeline - Basic CI/CD pipeline
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.6.0...HEAD [Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.7.2...v1.0.0
[0.6.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.6.0 [0.6.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.6.0
[0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5 [0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5
[0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4 [0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
+3 -2
View File
@@ -33,7 +33,7 @@ All tests and `tsc` must pass before committing.
## Architecture ## Architecture
``` ```text
src/ src/
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerDetailsViewSection, registerAppBarAction, registerPluginSettings; PolarisErrorBoundary ├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerDetailsViewSection, registerAppBarAction, registerPluginSettings; PolarisErrorBoundary
├── test-utils.tsx # Shared test fixtures (makeResult, makeAuditData) ├── test-utils.tsx # Shared test fixtures (makeResult, makeAuditData)
@@ -73,9 +73,10 @@ Data is fetched via `ApiProxy.request` to the Polaris dashboard service proxy an
## Testing ## Testing
Mock pattern for headlamp APIs: Mock pattern for headlamp APIs:
```typescript ```typescript
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
ApiProxy: { request: vi.fn().mockResolvedValue({}) }, ApiProxy: { request: vi.fn().mockResolvedValue({}) },
K8s: { ResourceClasses: {} }, K8s: { ResourceClasses: {} },
})); }));
``` ```
+16
View File
@@ -83,6 +83,7 @@ import { Box, Chip } from '@mui/material';
### Headlamp Component Issues ### Headlamp Component Issues
1. **StatusLabel with empty status** 1. **StatusLabel with empty status**
```typescript ```typescript
// ❌ Renders near-invisible (muted background) // ❌ Renders near-invisible (muted background)
<StatusLabel status="">{value}</StatusLabel> <StatusLabel status="">{value}</StatusLabel>
@@ -92,6 +93,7 @@ import { Box, Chip } from '@mui/material';
``` ```
2. **Link component crashes on plugin routes** 2. **Link component crashes on plugin routes**
```typescript ```typescript
// ❌ Headlamp Link crashes on plugin-registered routes // ❌ Headlamp Link crashes on plugin-registered routes
import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
@@ -210,6 +212,7 @@ npm run format:check
### Commit Convention ### Commit Convention
Use Conventional Commits: Use Conventional Commits:
- `feat:` - New feature - `feat:` - New feature
- `fix:` - Bug fix - `fix:` - Bug fix
- `docs:` - Documentation only - `docs:` - Documentation only
@@ -220,6 +223,7 @@ Use Conventional Commits:
### PR Process ### PR Process
All PRs must pass: All PRs must pass:
1. Build (`npm run build`) 1. Build (`npm run build`)
2. Lint (`npm run lint`) 2. Lint (`npm run lint`)
3. Type-check (`npm run tsc`) 3. Type-check (`npm run tsc`)
@@ -276,6 +280,7 @@ npm run e2e
### CI Workflow (`.github/workflows/ci.yaml`) ### CI Workflow (`.github/workflows/ci.yaml`)
Runs on push to main and all PRs: Runs on push to main and all PRs:
1. Checkout 1. Checkout
2. `npm ci` 2. `npm ci`
3. `npm run build` 3. `npm run build`
@@ -289,6 +294,7 @@ Runner: `local-ubuntu-latest`
### E2E Workflow (`.github/workflows/e2e.yaml`) ### E2E Workflow (`.github/workflows/e2e.yaml`)
Runs on push, PR, and manual trigger: Runs on push, PR, and manual trigger:
1. Checkout 1. Checkout
2. `npm ci` 2. `npm ci`
3. `npm run e2e` 3. `npm run e2e`
@@ -306,6 +312,7 @@ gh workflow run release.yaml -f version=0.4.2
``` ```
Steps: Steps:
1. Validate version format (semver) 1. Validate version format (semver)
2. Bump `package.json` + `artifacthub-pkg.yml` 2. Bump `package.json` + `artifacthub-pkg.yml`
3. Build plugin 3. Build plugin
@@ -323,6 +330,7 @@ Steps:
### Version Bump Requirements ### Version Bump Requirements
**ALWAYS bump both files in the same commit**: **ALWAYS bump both files in the same commit**:
- `package.json` - `version` field - `package.json` - `version` field
- `artifacthub-pkg.yml` - `version` field + `digest` (checksum) + `archive.url` - `artifacthub-pkg.yml` - `version` field + `digest` (checksum) + `archive.url`
@@ -331,12 +339,14 @@ Steps:
### ⚠️ Headlamp v0.39.0 Known Issues ### ⚠️ Headlamp v0.39.0 Known Issues
**AutoSizer JavaScript Error** **AutoSizer JavaScript Error**
- **Symptom**: Console shows `TypeError: undefined is not an object (evaluating 'io.AutoSizer')` - **Symptom**: Console shows `TypeError: undefined is not an object (evaluating 'io.AutoSizer')`
- **Impact**: Cosmetic error in Settings page, doesn't break functionality - **Impact**: Cosmetic error in Settings page, doesn't break functionality
- **Root Cause**: Headlamp core bug, not plugin-related - **Root Cause**: Headlamp core bug, not plugin-related
- **Workaround**: None needed, can be ignored - **Workaround**: None needed, can be ignored
**Plugin Loading (RESOLVED)** **Plugin Loading (RESOLVED)**
- **Old Issue**: Previously thought `config.watchPlugins: false` was required - **Old Issue**: Previously thought `config.watchPlugins: false` was required
- **Resolution**: Plugins load correctly with default `watchPlugins: true` - **Resolution**: Plugins load correctly with default `watchPlugins: true`
- **Note**: If you see old docs mentioning `watchPlugins: false`, ignore them - **Note**: If you see old docs mentioning `watchPlugins: false`, ignore them
@@ -344,28 +354,34 @@ Steps:
### Polaris Dashboard Behavior ### Polaris Dashboard Behavior
**Stale Audit Data** **Stale Audit Data**
- **Symptom**: Plugin shows old audit timestamp - **Symptom**: Plugin shows old audit timestamp
- **Root Cause**: Polaris dashboard runs audit once at pod startup, then caches results - **Root Cause**: Polaris dashboard runs audit once at pod startup, then caches results
- **Does NOT**: Continuously re-audit in real-time - **Does NOT**: Continuously re-audit in real-time
- **Workaround**: Restart Polaris pods for fresh data - **Workaround**: Restart Polaris pods for fresh data
```bash ```bash
kubectl rollout restart deployment -n polaris polaris-dashboard kubectl rollout restart deployment -n polaris polaris-dashboard
``` ```
- **Load Balancing**: Service balances across multiple pods - each may have different audit timestamps - **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 - **Plugin Auto-Refresh**: Works correctly - just fetches whatever Polaris currently has cached
### Skipped Count Limitation ### Skipped Count Limitation
**What It Shows**: **What It Shows**:
- Only checks with `Severity: "ignore"` in Polaris API response - Only checks with `Severity: "ignore"` in Polaris API response
- Does NOT include annotation-based exemptions (`polaris.fairwinds.com/*-exempt`) - Does NOT include annotation-based exemptions (`polaris.fairwinds.com/*-exempt`)
**Why**: **Why**:
- Polaris omits exempted checks from `results.json` - Polaris omits exempted checks from `results.json`
- Plugin has no access to raw K8s resources to compute exemptions - Plugin has no access to raw K8s resources to compute exemptions
- By design: service proxy limitation - By design: service proxy limitation
**Workaround**: **Workaround**:
- Link to native Polaris dashboard for full exemption count - Link to native Polaris dashboard for full exemption count
- UI tooltip explains this limitation - UI tooltip explains this limitation
+26
View File
@@ -18,6 +18,7 @@ Thank you for your interest in contributing to the Headlamp Polaris Plugin! This
## Code of Conduct ## Code of Conduct
This project follows a standard code of conduct: This project follows a standard code of conduct:
- Be respectful and inclusive - Be respectful and inclusive
- Welcome newcomers and help them get started - Welcome newcomers and help them get started
- Focus on constructive feedback - Focus on constructive feedback
@@ -35,23 +36,27 @@ This project follows a standard code of conduct:
### Development Setup ### Development Setup
1. **Fork and clone the repository:** 1. **Fork and clone the repository:**
```bash ```bash
git clone https://github.com/YOUR_USERNAME/headlamp-polaris-plugin.git git clone https://github.com/YOUR_USERNAME/headlamp-polaris-plugin.git
cd headlamp-polaris-plugin cd headlamp-polaris-plugin
``` ```
2. **Install dependencies:** 2. **Install dependencies:**
```bash ```bash
npm install npm install
``` ```
3. **Start development mode:** 3. **Start development mode:**
```bash ```bash
npm start npm start
# Plugin will be available at http://localhost:4466 # Plugin will be available at http://localhost:4466
``` ```
4. **Run tests:** 4. **Run tests:**
```bash ```bash
# Unit tests # Unit tests
npm test npm test
@@ -61,6 +66,7 @@ This project follows a standard code of conduct:
``` ```
5. **Build the plugin:** 5. **Build the plugin:**
```bash ```bash
npm run build npm run build
``` ```
@@ -79,18 +85,21 @@ This project follows a standard code of conduct:
### Local Testing ### Local Testing
**Option 1: Development Mode** **Option 1: Development Mode**
```bash ```bash
npm start npm start
# Opens Headlamp at http://localhost:4466 with hot reload # Opens Headlamp at http://localhost:4466 with hot reload
``` ```
**Option 2: Production Build** **Option 2: Production Build**
```bash ```bash
npm run build npm run build
# Plugin bundle created in dist/ # Plugin bundle created in dist/
``` ```
**Option 3: E2E Testing** **Option 3: E2E Testing**
```bash ```bash
# Set up environment (see e2e/README.md) # Set up environment (see e2e/README.md)
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h) export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
@@ -116,6 +125,7 @@ npm run e2e
- Chores: `chore/description` - Chores: `chore/description`
**Examples:** **Examples:**
```bash ```bash
feat/add-exemption-support feat/add-exemption-support
fix/dark-mode-theme-colors fix/dark-mode-theme-colors
@@ -127,16 +137,19 @@ chore/upgrade-dependencies
### Branching Rules ### Branching Rules
**✅ ALWAYS use feature branches for:** **✅ ALWAYS use feature branches for:**
- Code changes (new features, bug fixes, refactors) - Code changes (new features, bug fixes, refactors)
- Test updates - Test updates
- CI/CD workflow changes - CI/CD workflow changes
- Package updates - Package updates
**✅ MAY push directly to main for:** **✅ MAY push directly to main for:**
- Documentation-only changes (README.md, CLAUDE.md, comments) - Documentation-only changes (README.md, CLAUDE.md, comments)
- Version bump commits (`package.json` + `artifacthub-pkg.yml`) - Version bump commits (`package.json` + `artifacthub-pkg.yml`)
**❌ NEVER push directly to main for:** **❌ NEVER push directly to main for:**
- Any code changes to `src/` - Any code changes to `src/`
- Test file changes - Test file changes
- Workflow changes - Workflow changes
@@ -206,6 +219,7 @@ Co-Authored-By: Happy <yesreply@happy.engineering>
### Before Creating a PR ### Before Creating a PR
1. **Run all checks locally:** 1. **Run all checks locally:**
```bash ```bash
npm run build # Verify build succeeds npm run build # Verify build succeeds
npm run lint # Check for linting errors npm run lint # Check for linting errors
@@ -227,6 +241,7 @@ Co-Authored-By: Happy <yesreply@happy.engineering>
### Creating a PR ### Creating a PR
1. **Push your branch:** 1. **Push your branch:**
```bash ```bash
git push origin feat/your-feature git push origin feat/your-feature
``` ```
@@ -237,6 +252,7 @@ Co-Authored-By: Happy <yesreply@happy.engineering>
- Link related issues with `Fixes #123` or `Closes #456` - Link related issues with `Fixes #123` or `Closes #456`
3. **PR Title Format:** 3. **PR Title Format:**
``` ```
feat: add exemption management UI feat: add exemption management UI
fix: correct score calculation for skipped checks fix: correct score calculation for skipped checks
@@ -299,12 +315,14 @@ npm run format:check
### Import Organization ### Import Organization
Imports are automatically sorted by eslint. Order: Imports are automatically sorted by eslint. Order:
1. React imports 1. React imports
2. Third-party libraries 2. Third-party libraries
3. Headlamp plugin imports 3. Headlamp plugin imports
4. Local imports (components, API, types) 4. Local imports (components, API, types)
Example: Example:
```typescript ```typescript
import React from 'react'; import React from 'react';
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
@@ -330,6 +348,7 @@ import { computeScore } from '../api/polaris';
- Use descriptive test names - Use descriptive test names
Example: Example:
```typescript ```typescript
describe('countResults', () => { describe('countResults', () => {
it('counts passing, warning, and danger results correctly', () => { it('counts passing, warning, and danger results correctly', () => {
@@ -371,14 +390,17 @@ npm run e2e:headed
When making changes, update relevant documentation: When making changes, update relevant documentation:
#### Code Changes #### Code Changes
- **README.md:** User-facing features, installation, configuration - **README.md:** User-facing features, installation, configuration
- **CLAUDE.md:** Architecture, constraints, MCP integrations - **CLAUDE.md:** Architecture, constraints, MCP integrations
- **JSDoc:** All public APIs, components, hooks - **JSDoc:** All public APIs, components, hooks
#### Test Changes #### Test Changes
- **e2e/README.md:** New test scenarios or setup changes - **e2e/README.md:** New test scenarios or setup changes
#### Build/CI Changes #### Build/CI Changes
- **README.md:** Build commands, release process - **README.md:** Build commands, release process
- **.github/workflows/*.yaml:** Workflow comments - **.github/workflows/*.yaml:** Workflow comments
@@ -405,6 +427,7 @@ export function countResults(data: AuditData): ResultCounts {
### Version Numbering ### Version Numbering
We follow [Semantic Versioning](https://semver.org/): We follow [Semantic Versioning](https://semver.org/):
- **Major (1.0.0):** Breaking changes - **Major (1.0.0):** Breaking changes
- **Minor (0.1.0):** New features, backward compatible - **Minor (0.1.0):** New features, backward compatible
- **Patch (0.0.1):** Bug fixes, backward compatible - **Patch (0.0.1):** Bug fixes, backward compatible
@@ -416,6 +439,7 @@ We follow [Semantic Versioning](https://semver.org/):
1. **Merge feature PRs to main** 1. **Merge feature PRs to main**
2. **Bump version:** 2. **Bump version:**
```bash ```bash
# Edit package.json and artifacthub-pkg.yml # Edit package.json and artifacthub-pkg.yml
# Update version and archive-url # Update version and archive-url
@@ -425,6 +449,7 @@ We follow [Semantic Versioning](https://semver.org/):
``` ```
3. **Create and push tag:** 3. **Create and push tag:**
```bash ```bash
git tag vX.Y.Z git tag vX.Y.Z
git push origin vX.Y.Z git push origin vX.Y.Z
@@ -441,6 +466,7 @@ We follow [Semantic Versioning](https://semver.org/):
### Pre-release Versions ### Pre-release Versions
For testing before stable release: For testing before stable release:
- Use `-dev.N` suffix: `v0.3.5-dev.1` - Use `-dev.N` suffix: `v0.3.5-dev.1`
- Follow same process as stable releases - Follow same process as stable releases
- Mark as "pre-release" on GitHub - Mark as "pre-release" on GitHub
+48 -5
View File
@@ -13,10 +13,12 @@ This assessment identifies critical issues and improvement opportunities for the
## 🔴 Critical Issues (Must Fix Immediately) ## 🔴 Critical Issues (Must Fix Immediately)
### 1. TypeScript Compilation Errors ### 1. TypeScript Compilation Errors
**Severity:** CRITICAL **Severity:** CRITICAL
**Impact:** Build failures, type safety compromised **Impact:** Build failures, type safety compromised
**Issues:** **Issues:**
- `src/index.tsx:72` - `registerDetailsViewSection` expects 1 argument, got 2 - `src/index.tsx:72` - `registerDetailsViewSection` expects 1 argument, got 2
- `src/index.tsx:87` - `registerAppBarAction` expects 1 argument, got 2 - `src/index.tsx:87` - `registerAppBarAction` expects 1 argument, got 2
@@ -24,6 +26,7 @@ This assessment identifies critical issues and improvement opportunities for the
Update Headlamp plugin API calls to match the current version. Check @kinvolk/headlamp-plugin version compatibility. Update Headlamp plugin API calls to match the current version. Check @kinvolk/headlamp-plugin version compatibility.
**Action Items:** **Action Items:**
- [ ] Review Headlamp plugin API documentation - [ ] Review Headlamp plugin API documentation
- [ ] Update `registerDetailsViewSection` and `registerAppBarAction` calls - [ ] Update `registerDetailsViewSection` and `registerAppBarAction` calls
- [ ] Run `npm run tsc` to verify fixes - [ ] Run `npm run tsc` to verify fixes
@@ -32,6 +35,7 @@ Update Headlamp plugin API calls to match the current version. Check @kinvolk/he
--- ---
### 2. Production Plugin Loading Failure ### 2. Production Plugin Loading Failure
**Severity:** CRITICAL **Severity:** CRITICAL
**Impact:** Plugin is completely non-functional in production **Impact:** Plugin is completely non-functional in production
@@ -39,11 +43,13 @@ Update Headlamp plugin API calls to match the current version. Check @kinvolk/he
Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugins as "development directory" plugins, preventing frontend JavaScript execution. Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugins as "development directory" plugins, preventing frontend JavaScript execution.
**Current Status:** **Current Status:**
- Deployment patched to install plugins to `/headlamp/static-plugins` - Deployment patched to install plugins to `/headlamp/static-plugins`
- `watchPlugins: false` configured - `watchPlugins: false` configured
- Waiting for user to test if plugins now load - Waiting for user to test if plugins now load
**Action Items:** **Action Items:**
- [ ] Confirm plugins load after recent deployment changes - [ ] Confirm plugins load after recent deployment changes
- [ ] Document the fix in deployment guide - [ ] Document the fix in deployment guide
- [ ] Update MEMORY.md with final resolution - [ ] Update MEMORY.md with final resolution
@@ -52,15 +58,18 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 3. Test Failures ### 3. Test Failures
**Severity:** HIGH **Severity:** HIGH
**Impact:** CI failures, reduced confidence in changes **Impact:** CI failures, reduced confidence in changes
**Current Status:** **Current Status:**
- 1 test file failing (DashboardView) - 1 test file failing (DashboardView)
- 49 tests passing - 49 tests passing
- Error related to `SimpleTable` component mock - Error related to `SimpleTable` component mock
**Action Items:** **Action Items:**
- [ ] Fix DashboardView test mocking - [ ] Fix DashboardView test mocking
- [ ] Ensure all tests pass before merging PRs - [ ] Ensure all tests pass before merging PRs
- [ ] Add test for top issues feature - [ ] Add test for top issues feature
@@ -71,16 +80,19 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
## 🟡 High Priority Improvements ## 🟡 High Priority Improvements
### 4. Type Safety Enhancements ### 4. Type Safety Enhancements
**Severity:** HIGH **Severity:** HIGH
**Impact:** Better developer experience, catch errors earlier **Impact:** Better developer experience, catch errors earlier
**Recommendations:** **Recommendations:**
- Enable stricter TypeScript checks in `tsconfig.json` - Enable stricter TypeScript checks in `tsconfig.json`
- Add type definitions for all Headlamp plugin APIs - Add type definitions for all Headlamp plugin APIs
- Ensure no `any` types in production code - Ensure no `any` types in production code
- Add JSDoc comments for complex types - Add JSDoc comments for complex types
**Action Items:** **Action Items:**
- [ ] Audit codebase for `any` types - [ ] Audit codebase for `any` types
- [ ] Enable `noImplicitAny` and `strictNullChecks` - [ ] Enable `noImplicitAny` and `strictNullChecks`
- [ ] Add type guards for API responses - [ ] Add type guards for API responses
@@ -89,21 +101,25 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 5. Security Hardening ### 5. Security Hardening
**Severity:** HIGH **Severity:** HIGH
**Impact:** Prevent vulnerabilities, protect user data **Impact:** Prevent vulnerabilities, protect user data
**Current Risks:** **Current Risks:**
- Direct Kubernetes API access via service proxy - Direct Kubernetes API access via service proxy
- User input in exemption annotations (potential injection) - User input in exemption annotations (potential injection)
- External URL configuration for Polaris dashboard - External URL configuration for Polaris dashboard
**Recommendations:** **Recommendations:**
- Validate and sanitize all user inputs - Validate and sanitize all user inputs
- Implement input validation for dashboard URL - Implement input validation for dashboard URL
- Add CSRF protection for exemption management - Add CSRF protection for exemption management
- Audit dependencies for known vulnerabilities - Audit dependencies for known vulnerabilities
**Action Items:** **Action Items:**
- [ ] Add input validation utilities - [ ] Add input validation utilities
- [ ] Sanitize exemption annotation values - [ ] Sanitize exemption annotation values
- [ ] Validate URL format for dashboard configuration - [ ] Validate URL format for dashboard configuration
@@ -113,21 +129,25 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 6. Error Handling & User Experience ### 6. Error Handling & User Experience
**Severity:** MEDIUM **Severity:** MEDIUM
**Impact:** Better error messages, improved debugging **Impact:** Better error messages, improved debugging
**Current Gaps:** **Current Gaps:**
- Generic error messages don't help users troubleshoot - Generic error messages don't help users troubleshoot
- No retry logic for transient API failures - No retry logic for transient API failures
- Missing loading states in some components - Missing loading states in some components
**Recommendations:** **Recommendations:**
- Provide specific, actionable error messages - Provide specific, actionable error messages
- Implement retry logic with exponential backoff - Implement retry logic with exponential backoff
- Add loading skeletons for all async operations - Add loading skeletons for all async operations
- Show connection test results with specific failure reasons - Show connection test results with specific failure reasons
**Action Items:** **Action Items:**
- [ ] Create error message constants with solutions - [ ] Create error message constants with solutions
- [ ] Add retry logic to API calls - [ ] Add retry logic to API calls
- [ ] Implement loading skeletons - [ ] Implement loading skeletons
@@ -138,21 +158,25 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
## 🟢 Medium Priority Enhancements ## 🟢 Medium Priority Enhancements
### 7. Testing Coverage ### 7. Testing Coverage
**Severity:** MEDIUM **Severity:** MEDIUM
**Impact:** Confidence in changes, regression prevention **Impact:** Confidence in changes, regression prevention
**Current Coverage:** **Current Coverage:**
- Unit tests: Good coverage for API utilities - Unit tests: Good coverage for API utilities
- Component tests: Some coverage, gaps exist - Component tests: Some coverage, gaps exist
- E2E tests: Minimal (Playwright configured but underutilized) - E2E tests: Minimal (Playwright configured but underutilized)
**Recommendations:** **Recommendations:**
- Add E2E tests for critical user flows - Add E2E tests for critical user flows
- Test error scenarios and edge cases - Test error scenarios and edge cases
- Add visual regression tests - Add visual regression tests
- Test RBAC permission denied scenarios - Test RBAC permission denied scenarios
**Action Items:** **Action Items:**
- [ ] Write E2E test for complete audit workflow - [ ] Write E2E test for complete audit workflow
- [ ] Add tests for error states - [ ] Add tests for error states
- [ ] Test exemption management flow - [ ] Test exemption management flow
@@ -161,16 +185,19 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 8. Performance Optimization ### 8. Performance Optimization
**Severity:** MEDIUM **Severity:** MEDIUM
**Impact:** Faster load times, better UX **Impact:** Faster load times, better UX
**Opportunities:** **Opportunities:**
- Memoize expensive calculations (score computation) - Memoize expensive calculations (score computation)
- Lazy load namespace detail views - Lazy load namespace detail views
- Debounce search/filter operations - Debounce search/filter operations
- Cache Polaris data with stale-while-revalidate - Cache Polaris data with stale-while-revalidate
**Action Items:** **Action Items:**
- [ ] Add React.memo to pure components - [ ] Add React.memo to pure components
- [ ] Memoize score calculations - [ ] Memoize score calculations
- [ ] Implement data caching strategy - [ ] Implement data caching strategy
@@ -179,16 +206,19 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 9. Code Quality & Maintainability ### 9. Code Quality & Maintainability
**Severity:** MEDIUM **Severity:** MEDIUM
**Impact:** Easier maintenance, onboarding **Impact:** Easier maintenance, onboarding
**Recommendations:** **Recommendations:**
- Extract magic strings to constants - Extract magic strings to constants
- Reduce component complexity - Reduce component complexity
- Add JSDoc comments for public APIs - Add JSDoc comments for public APIs
- Improve code organization - Improve code organization
**Action Items:** **Action Items:**
- [ ] Create constants file for check IDs - [ ] Create constants file for check IDs
- [ ] Split large components (DashboardView, NamespaceDetailView) - [ ] Split large components (DashboardView, NamespaceDetailView)
- [ ] Add comments for complex logic - [ ] Add comments for complex logic
@@ -199,16 +229,19 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
## 🔵 Low Priority / Future Enhancements ## 🔵 Low Priority / Future Enhancements
### 10. Documentation ### 10. Documentation
**Severity:** LOW **Severity:** LOW
**Impact:** Better onboarding, user adoption **Impact:** Better onboarding, user adoption
**Gaps:** **Gaps:**
- No architecture documentation - No architecture documentation
- Limited inline code comments - Limited inline code comments
- Missing troubleshooting guide - Missing troubleshooting guide
- No contributor guidelines - No contributor guidelines
**Action Items:** **Action Items:**
- [ ] Create architecture diagram - [ ] Create architecture diagram
- [ ] Document component hierarchy - [ ] Document component hierarchy
- [ ] Add troubleshooting section to README - [ ] Add troubleshooting section to README
@@ -217,16 +250,19 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
--- ---
### 11. CI/CD Pipeline Optimization ### 11. CI/CD Pipeline Optimization
**Severity:** LOW **Severity:** LOW
**Impact:** Faster feedback, automated releases **Impact:** Faster feedback, automated releases
**Opportunities:** **Opportunities:**
- Run tests in parallel - Run tests in parallel
- Cache npm dependencies - Cache npm dependencies
- Add automated security scanning - Add automated security scanning
- Implement semantic versioning - Implement semantic versioning
**Action Items:** **Action Items:**
- [ ] Parallelize test execution - [ ] Parallelize test execution
- [ ] Add npm cache to GitHub Actions - [ ] Add npm cache to GitHub Actions
- [ ] Integrate Dependabot - [ ] Integrate Dependabot
@@ -237,41 +273,48 @@ Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugin
## Summary & Prioritization ## Summary & Prioritization
### Week 1 (Immediate) ### Week 1 (Immediate)
1. ✅ Fix TypeScript compilation errors 1. ✅ Fix TypeScript compilation errors
2. ✅ Resolve production plugin loading issue 2. ✅ Resolve production plugin loading issue
3. ✅ Fix failing DashboardView test 3. ✅ Fix failing DashboardView test
### Week 2 (High Priority) ### Week 2 (High Priority)
4. Enhance type safety (strict mode) 4. Enhance type safety (strict mode)
5. Implement security hardening 2. Implement security hardening
6. Improve error handling and UX 3. Improve error handling and UX
### Week 3-4 (Medium Priority) ### Week 3-4 (Medium Priority)
7. Increase test coverage to >80% 7. Increase test coverage to >80%
8. Optimize performance (memoization, caching) 2. Optimize performance (memoization, caching)
9. Refactor for maintainability 3. Refactor for maintainability
### Ongoing (Low Priority) ### Ongoing (Low Priority)
10. Documentation improvements 10. Documentation improvements
11. CI/CD optimizations 2. CI/CD optimizations
--- ---
## Success Metrics ## Success Metrics
**Code Quality:** **Code Quality:**
- ✅ Zero TypeScript errors - ✅ Zero TypeScript errors
- ✅ All tests passing - ✅ All tests passing
- 🎯 Test coverage >80% - 🎯 Test coverage >80%
- 🎯 No high/critical security vulnerabilities - 🎯 No high/critical security vulnerabilities
**Production Readiness:** **Production Readiness:**
- ✅ Plugin loads successfully in Headlamp - ✅ Plugin loads successfully in Headlamp
- ✅ All features functional - ✅ All features functional
- 🎯 Error rate <1% - 🎯 Error rate <1%
- 🎯 Average response time <500ms - 🎯 Average response time <500ms
**Developer Experience:** **Developer Experience:**
- ✅ Clear documentation - ✅ Clear documentation
- ✅ Easy local setup - ✅ Easy local setup
- 🎯 Fast CI/CD (<5 min) - 🎯 Fast CI/CD (<5 min)
+2 -2
View File
@@ -206,7 +206,7 @@ For complete testing guide including CI/CD integration, see **[docs/TESTING.md](
## Project Structure ## Project Structure
``` ```text
src/ src/
index.tsx -- Entry point. Registers sidebar entries, routes, and error boundaries. index.tsx -- Entry point. Registers sidebar entries, routes, and error boundaries.
test-utils.tsx -- Shared test fixtures (makeResult, makeAuditData). test-utils.tsx -- Shared test fixtures (makeResult, makeAuditData).
@@ -236,7 +236,7 @@ GET /api/v1/namespaces/polaris/services/polaris-dashboard/proxy/results.json
This endpoint is served by the `polaris-dashboard` ClusterIP service, which is created by the Polaris Helm chart when `dashboard.enabled: true`. The JSON response matches Polaris's `AuditData` schema (`pkg/validator/output.go`): This endpoint is served by the `polaris-dashboard` ClusterIP service, which is created by the Polaris Helm chart when `dashboard.enabled: true`. The JSON response matches Polaris's `AuditData` schema (`pkg/validator/output.go`):
``` ```text
AuditData AuditData
ClusterInfo -- nodes, pods, namespaces, controllers ClusterInfo -- nodes, pods, namespaces, controllers
Results[] -- per-workload results Results[] -- per-workload results
+10 -1
View File
@@ -17,7 +17,7 @@ The plugin performs **only read operations** via the Kubernetes API server's ser
### Data Flow ### Data Flow
``` ```text
User Browser User Browser
↓ (HTTPS) ↓ (HTTPS)
Headlamp Pod Headlamp Pod
@@ -152,6 +152,7 @@ spec:
Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings. Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings.
**Security Considerations:** **Security Considerations:**
- All users have identical access to the plugin - All users have identical access to the plugin
- Suitable for trusted internal environments - Suitable for trusted internal environments
- Simpler RBAC management - Simpler RBAC management
@@ -161,6 +162,7 @@ Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). Al
Headlamp can be configured for OIDC authentication, where each user provides their own bearer token. RBAC is enforced per-user. Headlamp can be configured for OIDC authentication, where each user provides their own bearer token. RBAC is enforced per-user.
**Security Considerations:** **Security Considerations:**
- Fine-grained access control per user - Fine-grained access control per user
- Users without the `polaris-proxy-reader` role will see 403 errors - Users without the `polaris-proxy-reader` role will see 403 errors
- Requires OIDC provider integration - Requires OIDC provider integration
@@ -198,10 +200,12 @@ If you discover a security vulnerability in this plugin, please report it via:
2. **Email**: Create a GitHub issue and mark it as "security" if advisories are not available 2. **Email**: Create a GitHub issue and mark it as "security" if advisories are not available
**Please do not:** **Please do not:**
- Open public GitHub issues for security vulnerabilities - Open public GitHub issues for security vulnerabilities
- Disclose vulnerabilities publicly before a fix is available - Disclose vulnerabilities publicly before a fix is available
**Response Timeline:** **Response Timeline:**
- **Acknowledgment**: Within 48 hours - **Acknowledgment**: Within 48 hours
- **Initial Assessment**: Within 1 week - **Initial Assessment**: Within 1 week
- **Fix Timeline**: Depends on severity (critical: 1-2 weeks, high: 2-4 weeks, medium/low: next release cycle) - **Fix Timeline**: Depends on severity (critical: 1-2 weeks, high: 2-4 weeks, medium/low: next release cycle)
@@ -211,6 +215,7 @@ If you discover a security vulnerability in this plugin, please report it via:
### Dependency Scanning ### Dependency Scanning
The project uses: The project uses:
- **npm audit**: Runs automatically during `npm install` - **npm audit**: Runs automatically during `npm install`
- **Dependabot**: GitHub Dependabot monitors dependencies and creates PRs for updates - **Dependabot**: GitHub Dependabot monitors dependencies and creates PRs for updates
- **GitHub Actions**: CI workflow runs `npm audit` on every commit - **GitHub Actions**: CI workflow runs `npm audit` on every commit
@@ -262,6 +267,7 @@ The plugin's security posture depends on your cluster's security:
**Cause**: User or service account lacks `services/proxy` permission on `polaris-dashboard` **Cause**: User or service account lacks `services/proxy` permission on `polaris-dashboard`
**Resolution**: **Resolution**:
1. Verify RoleBinding exists in `polaris` namespace 1. Verify RoleBinding exists in `polaris` namespace
2. Check RoleBinding references correct subject (service account, group, or user) 2. Check RoleBinding references correct subject (service account, group, or user)
3. Confirm Role includes `resourceNames: ["polaris-dashboard"]` 3. Confirm Role includes `resourceNames: ["polaris-dashboard"]`
@@ -273,11 +279,13 @@ The plugin's security posture depends on your cluster's security:
**Question**: Can I expose Polaris dashboard via Ingress instead of using service proxy? **Question**: Can I expose Polaris dashboard via Ingress instead of using service proxy?
**Recommendation**: **Avoid exposing Polaris dashboard externally**. The service proxy approach: **Recommendation**: **Avoid exposing Polaris dashboard externally**. The service proxy approach:
- Enforces Kubernetes RBAC on every request - Enforces Kubernetes RBAC on every request
- Avoids exposing internal services to the internet - Avoids exposing internal services to the internet
- Prevents authentication bypass attacks - Prevents authentication bypass attacks
If you must expose Polaris externally: If you must expose Polaris externally:
- Use OAuth2 proxy or similar authentication layer - Use OAuth2 proxy or similar authentication layer
- Configure NetworkPolicies to restrict access - Configure NetworkPolicies to restrict access
- Enable TLS with valid certificates - Enable TLS with valid certificates
@@ -304,6 +312,7 @@ Users not in `team-a` will receive 403 errors when accessing the plugin, prevent
### Data Residency ### Data Residency
All data remains within your Kubernetes cluster. The plugin does not: All data remains within your Kubernetes cluster. The plugin does not:
- Send data to external services - Send data to external services
- Store data in browser localStorage (except refresh interval preference) - Store data in browser localStorage (except refresh interval preference)
- Use third-party analytics or tracking - Use third-party analytics or tracking
+46 -4
View File
@@ -1,4 +1,4 @@
version: "0.7.1" version: "1.0.0"
name: headlamp-polaris name: headlamp-polaris
displayName: Polaris displayName: Polaris
createdAt: "2026-02-05T19:00:00Z" createdAt: "2026-02-05T19:00:00Z"
@@ -11,6 +11,7 @@ description: >-
`polaris-dashboard` service in the `polaris` namespace. `polaris-dashboard` service in the `polaris` namespace.
license: Apache-2.0 license: Apache-2.0
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin" homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
appVersion: "10.1.6"
category: security category: security
keywords: keywords:
- polaris - polaris
@@ -24,11 +25,52 @@ links:
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin" url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
- name: Polaris - name: Polaris
url: "https://polaris.docs.fairwinds.com/" url: "https://polaris.docs.fairwinds.com/"
install: |
## Installation
### Prerequisites
1. [Headlamp](https://headlamp.dev) v0.26.0 or later
2. [Fairwinds Polaris](https://polaris.docs.fairwinds.com/) installed and the dashboard running in your cluster
### Install via Headlamp Plugin Catalog
1. Open Headlamp and navigate to **Settings → Plugin Catalog**
2. Search for **"Polaris"**
3. Click **Install** and restart Headlamp when prompted
The plugin is sourced directly from [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp/headlamp-polaris).
## Usage
After installation, the Polaris plugin adds:
- A **cluster score badge** in the Headlamp app bar
- A **Polaris** section in the sidebar with the full dashboard and namespace drill-downs
- An **inline audit panel** on Deployment, StatefulSet, DaemonSet, Job, and CronJob detail pages
For more information, see the [README](https://github.com/privilegedescalation/headlamp-polaris-plugin/blob/main/README.md).
changes:
- kind: security
description: Patched 8 npm audit vulnerabilities via pnpm.overrides
- kind: added
description: Dual-approval required CI check — PRs must be approved by both CTO and QA before merge
- kind: added
description: ExemptionManager test suite — full coverage of annotation-based exemption flows
- kind: fixed
description: E2E infrastructure overhauled — ConfigMap volume mount replaces Dockerfile-based approach, tests run in privilegedescalation-dev namespace
- kind: fixed
description: E2E workflow uses token auth and waits for HTTP reachability before running tests
- kind: fixed
description: Added explicit direct devDependencies (typescript, eslint, prettier, @headlamp-k8s/eslint-config) to prevent phantom dep failures
- kind: changed
description: pnpm version pinned via packageManager field; GitHub Actions SHA-pinned via Renovate pinDigests
- kind: changed
description: v1.0.0 stable release — plugin API (routes, sidebar, settings schema, app bar action) is stable and will not change without a major version bump
maintainers: maintainers:
- name: privilegedescalation - name: privilegedescalation
email: "chris@farhood.org" email: "chris@farhood.org"
annotations: annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.1/headlamp-polaris-0.7.1.tar.gz" headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v1.0.0/headlamp-polaris-1.0.0.tar.gz"
headlamp/plugin/version-compat: ">=0.26" headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/archive-checksum: sha256:5ea27f3beeab9730a13a752a0a7c2270eca83dcbbe813a6cabeb6a22fa9d5174 headlamp/plugin/archive-checksum: sha256:a165e871b40f11a44950aa9f10eb7f7883276f749026ae7a4f886278ecd9bd7d
headlamp/plugin/distro-compat: in-cluster headlamp/plugin/distro-compat: "in-cluster,web,desktop"
+13 -2
View File
@@ -1,19 +1,24 @@
# Headlamp Plugin Loading Issue - Root Cause and Fix # Headlamp Plugin Loading Issue - Root Cause and Fix
## Problem ## Problem
Headlamp v0.39.0 was not loading plugins installed via the plugin manager. Plugins appeared in Settings → Plugins but: Headlamp v0.39.0 was not loading plugins installed via the plugin manager. Plugins appeared in Settings → Plugins but:
- No sidebar entries appeared - No sidebar entries appeared
- No plugin settings were available - No plugin settings were available
- Plugin JavaScript was not being executed in the browser - Plugin JavaScript was not being executed in the browser
## Root Cause ## Root Cause
When `config.watchPlugins: true` (the default), Headlamp treats catalog-managed plugins in `/headlamp/plugins/` as "development directory" plugins. This causes: When `config.watchPlugins: true` (the default), Headlamp treats catalog-managed plugins in `/headlamp/plugins/` as "development directory" plugins. This causes:
- Backend serves plugin metadata correctly - Backend serves plugin metadata correctly
- Backend logs show "Treating catalog-installed plugin in development directory as user plugin" - Backend logs show "Treating catalog-installed plugin in development directory as user plugin"
- **Frontend does NOT execute the plugin JavaScript** - **Frontend does NOT execute the plugin JavaScript**
- Plugin registrations (`registerSidebarEntry`, `registerRoute`, etc.) never happen - Plugin registrations (`registerSidebarEntry`, `registerRoute`, etc.) never happen
## Solution ## Solution
Set `config.watchPlugins: false` in the Headlamp HelmRelease values: Set `config.watchPlugins: false` in the Headlamp HelmRelease values:
```yaml ```yaml
@@ -31,14 +36,18 @@ spec:
``` ```
## Why This Works ## Why This Works
With `watchPlugins: false`: With `watchPlugins: false`:
- Headlamp no longer treats catalog-managed plugins as "development" plugins - Headlamp no longer treats catalog-managed plugins as "development" plugins
- Frontend properly loads and executes plugin JavaScript on startup - Frontend properly loads and executes plugin JavaScript on startup
- Plugin registrations happen correctly - Plugin registrations happen correctly
- All plugin features (sidebar, routes, settings, etc.) work as expected - All plugin features (sidebar, routes, settings, etc.) work as expected
## Testing ## Testing
After applying this fix: After applying this fix:
1. Verify plugins are installed: `kubectl logs -n kube-system <headlamp-pod> -c headlamp-plugin` 1. Verify plugins are installed: `kubectl logs -n kube-system <headlamp-pod> -c headlamp-plugin`
2. Verify watchPlugins is false: `kubectl logs -n kube-system <headlamp-pod> -c headlamp | grep "Watch Plugins"` 2. Verify watchPlugins is false: `kubectl logs -n kube-system <headlamp-pod> -c headlamp | grep "Watch Plugins"`
3. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) to clear cached JavaScript 3. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) to clear cached JavaScript
@@ -46,13 +55,15 @@ After applying this fix:
5. Verify plugin functionality works 5. Verify plugin functionality works
## Additional Notes ## Additional Notes
- This appears to be a bug/limitation in Headlamp v0.39.0 - This appears to be a bug/limitation in Headlamp v0.39.0
- The `watchPlugins` feature is intended for development scenarios where plugins are being actively modified - The `watchPlugins` feature is intended for development scenarios where plugins are being actively modified
- For production deployments with catalog-managed plugins, `watchPlugins: false` is the correct configuration - For production deployments with catalog-managed plugins, `watchPlugins: false` is the correct configuration
- Once plugins are loaded, subsequent restarts or updates work correctly as long as `watchPlugins` remains false - Once plugins are loaded, subsequent restarts or updates work correctly as long as `watchPlugins` remains false
## References ## References
- Headlamp Helm Chart: https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp
- Plugin Manager: https://github.com/headlamp-k8s/headlamp/tree/main/plugins/headlamp-plugin - Headlamp Helm Chart: <https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp>
- Plugin Manager: <https://github.com/headlamp-k8s/headlamp/tree/main/plugins/headlamp-plugin>
- Issue discovered: 2026-02-11 - Issue discovered: 2026-02-11
- Fix applied: 2026-02-12 - Fix applied: 2026-02-12
+48 -32
View File
@@ -1,57 +1,73 @@
--- ---
# RBAC for the GitHub Actions CI runner to perform E2E test setup. # RBAC for the GitHub Actions CI runner to manage the E2E Headlamp instance.
# CI-only test fixture — NOT for production use. # CI-only test fixture — NOT for production use.
# #
# Grants the ARC runner service account namespace-scoped permissions in # Grants the ARC runner service account permissions in the privilegedescalation-dev
# kube-system to patch the Headlamp deployment (add shared volume mount), # namespace to deploy and tear down a dedicated Headlamp instance via Helm.
# manage PVCs, run temporary pods, and restart deployments. # E2E resources run in `privilegedescalation-dev` — nothing persists beyond a test run.
# #
# No cluster-scoped permissions needed — the E2E workflow uses kubectl patch # Plugin is loaded via ConfigMap volume mount — no custom Docker images.
# instead of helm upgrade, avoiding the need to read ClusterRole/ClusterRoleBinding.
# #
# Apply with: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml # Prerequisites:
# kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: e2e-ci-runner name: e2e-ci-runner
namespace: kube-system namespace: privilegedescalation-dev
rules: rules:
- apiGroups: [""] # Helm needs to manage these resources for the Headlamp chart
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create", "delete", "watch"]
- apiGroups: [""]
resources: ["pods/attach"]
verbs: ["create", "get"]
- apiGroups: ["apps"] - apiGroups: ["apps"]
resources: ["deployments"] resources: ["deployments"]
verbs: ["get", "list", "patch", "watch"] verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
- apiGroups: ["apps"]
resources: ["deployments/scale"]
verbs: ["patch"]
- apiGroups: [""] - apiGroups: [""]
resources: ["secrets"] resources: ["services", "serviceaccounts", "configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "patch", "delete"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""] - apiGroups: [""]
resources: ["configmaps"] resources: ["pods"]
verbs: ["get", "list", "create", "update", "patch", "delete"] verbs: ["get", "list", "watch"]
# Token creation for E2E test auth
- apiGroups: [""] - apiGroups: [""]
resources: ["services"] resources: ["serviceaccounts/token"]
verbs: ["get", "list", "create", "update", "patch", "delete"] verbs: ["create"]
- apiGroups: [""] # RBAC pre-flight check: verify polaris namespace has proxy-reader Role + RoleBinding
resources: ["serviceaccounts"] # before running E2E tests. Required by the "RBAC pre-flight check" workflow step.
verbs: ["get", "list"] - apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: e2e-ci-runner-polaris-reader
namespace: polaris
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: e2e-ci-runner-polaris-reader-binding
namespace: polaris
subjects:
- kind: ServiceAccount
name: runners-privilegedescalation-gha-rs-no-permission
namespace: arc-runners
roleRef:
kind: Role
name: e2e-ci-runner-polaris-reader
apiGroup: rbac.authorization.k8s.io
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: e2e-ci-runner-binding name: e2e-ci-runner-binding
namespace: kube-system namespace: privilegedescalation-dev
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: local-ubuntu-latest-gha-rs-no-permission name: runners-privilegedescalation-gha-rs-no-permission
namespace: arc-runners namespace: arc-runners
roleRef: roleRef:
kind: Role kind: Role
-22
View File
@@ -1,22 +0,0 @@
---
# Headlamp Helm values for E2E testing with shared volume plugin deployment.
#
# The CI runner and Headlamp pod share a PVC so that the runner can copy
# built plugin artifacts directly into Headlamp's plugins directory.
# This is a CI-only mechanism — production plugin distribution uses ArtifactHub.
# Point Headlamp at the shared plugins mount
config:
pluginsDir: /headlamp/plugins
# PVC-backed volume shared with the CI runner
volumes:
- name: plugins
persistentVolumeClaim:
claimName: headlamp-plugins
# Mount into the Headlamp container
volumeMounts:
- name: plugins
mountPath: /headlamp/plugins
readOnly: true
-14
View File
@@ -1,14 +0,0 @@
---
# PVC for sharing built plugin artifacts between the CI runner and Headlamp.
# Used only in E2E test environments — not for production.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: headlamp-plugins
namespace: kube-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 128Mi
+1 -1
View File
@@ -129,7 +129,7 @@ docs/
### 3. CHANGELOG.md Standard ### 3. CHANGELOG.md Standard
**Format**: Keep a Changelog (https://keepachangelog.com/) **Format**: Keep a Changelog (<https://keepachangelog.com/>)
**Structure**: **Structure**:
+1
View File
@@ -703,6 +703,7 @@ If none of these solutions work, gather debugging information and open an issue:
``` ```
6. **RBAC Configuration**: 6. **RBAC Configuration**:
```bash ```bash
kubectl get role,rolebinding -n polaris kubectl get role,rolebinding -n polaris
``` ```
+1
View File
@@ -703,6 +703,7 @@ If none of these solutions work, gather debugging information and open an issue:
``` ```
6. **RBAC Configuration**: 6. **RBAC Configuration**:
```bash ```bash
kubectl get role,rolebinding -n polaris kubectl get role,rolebinding -n polaris
``` ```
+1
View File
@@ -286,6 +286,7 @@ localStorage.removeItem('polaris-plugin-dashboard-url');
2. Check for JavaScript errors 2. Check for JavaScript errors
3. Disable privacy mode or try different browser 3. Disable privacy mode or try different browser
4. Check if localStorage is enabled: 4. Check if localStorage is enabled:
```javascript ```javascript
console.log('localStorage available:', typeof localStorage !== 'undefined'); console.log('localStorage available:', typeof localStorage !== 'undefined');
``` ```
+2
View File
@@ -508,9 +508,11 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
Expected: `yes` Expected: `yes`
4. **Verify RoleBinding subjects match:** 4. **Verify RoleBinding subjects match:**
```bash ```bash
kubectl -n polaris get rolebinding headlamp-polaris-proxy -o yaml kubectl -n polaris get rolebinding headlamp-polaris-proxy -o yaml
``` ```
Check `subjects[].name` and `subjects[].namespace` match your Headlamp SA or user Check `subjects[].name` and `subjects[].namespace` match your Headlamp SA or user
### "404 Not Found" Error ### "404 Not Found" Error
+40 -24
View File
@@ -4,7 +4,16 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
## CI ## CI
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`) uses either Authentik OIDC or token-based authentication via repository secrets. E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`):
1. Builds the plugin (`npm run build`)
2. Creates a ConfigMap from the built `dist/` output
3. Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume
4. Generates a ServiceAccount token for test auth
5. Runs Playwright tests against the E2E instance
6. Tears down the E2E instance
This approach uses the stock `ghcr.io/headlamp-k8s/headlamp` image with no custom Docker builds. The plugin is loaded via `HEADLAMP_PLUGINS_DIR` volume mount.
### Required GitHub Secrets ### Required GitHub Secrets
@@ -12,12 +21,12 @@ Configure these in GitHub repository settings (Settings → Secrets and variable
| Secret | Required | Description | | Secret | Required | Description |
| -------------------- | -------- | -------------------------------------------------------------- | | -------------------- | -------- | -------------------------------------------------------------- |
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to `https://headlamp.animaniacs.farh.net`) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access | | `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user | | `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set. Token-based auth is auto-generated by the deploy script. OIDC secrets are only needed if testing against the shared Headlamp instance.
No `GHCR_TOKEN` or Docker registry secrets are needed — the stock Headlamp image is public.
## Running Locally ## Running Locally
@@ -47,12 +56,12 @@ HEADLAMP_URL=http://localhost:4466 npm run e2e:headed
| Variable | Required | Default | Description | | Variable | Required | Default | Description |
| -------------------- | -------- | -------------------------------------- | --------------------------------------- | | -------------------- | -------- | -------------------------------------- | --------------------------------------- |
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance | | `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username | | `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password | | `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (fallback auth) | | `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (auto-generated in CI) |
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC takes priority if both are set. In CI, `HEADLAMP_URL` and `HEADLAMP_TOKEN` are set automatically by the deploy script. For local runs, set either OIDC credentials or a token manually.
## What the Tests Validate ## What the Tests Validate
@@ -106,6 +115,7 @@ These are smoke tests against real cluster data. They verify the plugin loads an
### Cluster Requirements ### Cluster Requirements
1. **Polaris Deployment** 1. **Polaris Deployment**
```bash ```bash
# Verify Polaris is running # Verify Polaris is running
kubectl -n polaris get pods kubectl -n polaris get pods
@@ -113,6 +123,7 @@ These are smoke tests against real cluster data. They verify the plugin loads an
``` ```
2. **Polaris Audit Data** 2. **Polaris Audit Data**
```bash ```bash
# Check if Polaris has generated audit results # Check if Polaris has generated audit results
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime' kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime'
@@ -174,21 +185,25 @@ Tests automatically capture screenshots on failure in `test-results/`
### Common Issues ### Common Issues
**Auth fails with "Sign In button not found":** **Auth fails with "Sign In button not found":**
- Check HEADLAMP_URL is correct - Check HEADLAMP_URL is correct
- Verify Headlamp is accessible - Verify Headlamp is accessible
- Ensure OIDC is configured if using Authentik - Ensure OIDC is configured if using Authentik
**Polaris sidebar entry not found:** **Polaris sidebar entry not found:**
- Plugin may not be installed: Check Settings → Plugins in Headlamp - Plugin may not be installed: Check Settings → Plugins in Headlamp
- Plugin may have failed to load: Check browser console - Plugin may have failed to load: Check browser console
- Clear browser cache and hard refresh - Clear browser cache and hard refresh
**Cluster score not displayed:** **Cluster score not displayed:**
- Polaris may not have audit data yet - Polaris may not have audit data yet
- Check Polaris is running: `kubectl -n polaris get pods` - Check Polaris is running: `kubectl -n polaris get pods`
- Verify service proxy: `kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json` - Verify service proxy: `kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
**Namespace table empty:** **Namespace table empty:**
- Polaris hasn't run audit yet (wait a few minutes) - Polaris hasn't run audit yet (wait a few minutes)
- Check Polaris logs: `kubectl -n polaris logs -l app.kubernetes.io/name=polaris` - Check Polaris logs: `kubectl -n polaris logs -l app.kubernetes.io/name=polaris`
@@ -249,29 +264,30 @@ test('plugin UI adapts to dark mode', async ({ page }) => {
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration. Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
### Required Secrets ### Architecture
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions): The E2E workflow deploys a **dedicated Headlamp instance** for each test run:
- `HEADLAMP_URL` (optional): Headlamp instance URL 1. Build plugin (`npm run build`)
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth) 2. Create ConfigMap from `dist/` output (`scripts/deploy-e2e-headlamp.sh`)
- OR `HEADLAMP_TOKEN` (for token-based auth) 3. Deploy stock Headlamp via Helm with ConfigMap volume mount
4. Run Playwright tests against the E2E instance
5. Tear down (`scripts/teardown-e2e-headlamp.sh`)
### Workflow Overview No custom Docker images, no PVCs, no kubectl exec/cp, no patching of existing deployments. The plugin is mounted from a ConfigMap into the stock Headlamp image.
1. Checkout code ### Cluster Prerequisites
2. Setup Node.js 20 with npm cache
3. Install dependencies (`npm ci`) One-time setup by a cluster admin:
4. Install Playwright browsers (`chromium` only)
5. Run auth setup (creates session in `e2e/.auth/state.json`) ```bash
6. Run all E2E tests kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
7. Upload artifacts on failure: ```
- `playwright-report/` - HTML test report
- `test-results/` - Screenshots, traces, videos
### Manual Trigger ### Manual Trigger
You can manually trigger E2E tests from GitHub Actions: You can manually trigger E2E tests from GitHub Actions:
1. Go to Actions → E2E Tests 1. Go to Actions → E2E Tests
2. Click "Run workflow" 2. Click "Run workflow"
3. Select branch and run 3. Select branch and run
+12 -5
View File
@@ -39,13 +39,20 @@ async function authenticateWithOIDC(page: Page, username: string, password: stri
} }
async function authenticateWithToken(page: Page, token: string): Promise<void> { async function authenticateWithToken(page: Page, token: string): Promise<void> {
// Navigate to login — Headlamp redirects / to /c/main/login
await page.goto('/'); await page.goto('/');
await page.waitForURL('**/login'); // Headlamp goes to /token directly when no OIDC is configured,
// or through /login when OIDC is configured
await page.waitForURL(/\/(login|token)$/);
// Click the token auth option if (page.url().includes('/login')) {
await page.getByRole('button', { name: /use a token/i }).click(); // OIDC login page — click "use a token" to reach token auth.
await page.waitForURL('**/token'); // Wait explicitly before clicking so failures surface at 15 s
// with a clear message rather than silently timing out at 60 s.
const useTokenBtn = page.getByRole('button', { name: /use a token/i });
await useTokenBtn.waitFor({ state: 'visible', timeout: 15_000 });
await useTokenBtn.click();
await page.waitForURL('**/token');
}
// Fill the "ID token" field and submit // Fill the "ID token" field and submit
await page.getByRole('textbox', { name: /id token/i }).fill(token); await page.getByRole('textbox', { name: /id token/i }).fill(token);
+91 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "headlamp-polaris", "name": "headlamp-polaris",
"version": "0.7.1", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "headlamp-polaris", "name": "headlamp-polaris",
"version": "0.7.1", "version": "1.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0", "@kinvolk/headlamp-plugin": "^0.13.0",
@@ -17,11 +17,13 @@
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitest/coverage-v8": "^3.2.4",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"tar": "^7.5.11", "tar": "^7.5.11",
"typescript": "~5.6.2",
"undici": "^7.24.3", "undici": "^7.24.3",
"vitest": "^3.0.5" "vitest": "^3.0.5"
}, },
@@ -37,6 +39,20 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": { "node_modules/@apidevtools/json-schema-ref-parser": {
"version": "11.7.2", "version": "11.7.2",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz",
@@ -431,6 +447,16 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@bundled-es-modules/cookie": { "node_modules/@bundled-es-modules/cookie": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
@@ -4846,6 +4872,40 @@
"vitest": "3.2.4" "vitest": "3.2.4"
} }
}, },
"node_modules/@vitest/coverage-v8": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
"ast-v8-to-istanbul": "^0.3.3",
"debug": "^4.4.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.1.7",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"std-env": "^3.9.0",
"test-exclude": "^7.0.1",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "3.2.4",
"vitest": "3.2.4"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
@@ -5651,6 +5711,35 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
"integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^10.0.0"
}
},
"node_modules/ast-v8-to-istanbul/node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/astral-regex": { "node_modules/astral-regex": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+13 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "headlamp-polaris", "name": "headlamp-polaris",
"version": "0.7.1", "version": "1.0.0",
"description": "Headlamp plugin for Fairwinds Polaris audit results", "description": "Headlamp plugin for Fairwinds Polaris audit results",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -12,6 +12,7 @@
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme", "homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
"author": "privilegedescalation", "author": "privilegedescalation",
"license": "Apache-2.0", "license": "Apache-2.0",
"packageManager": "pnpm@10.32.1",
"scripts": { "scripts": {
"start": "headlamp-plugin start", "start": "headlamp-plugin start",
"build": "headlamp-plugin build", "build": "headlamp-plugin build",
@@ -30,9 +31,12 @@
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0" "react-dom": "^18.0.0"
}, },
"overrides": { "pnpm": {
"tar": "^7.5.11", "overrides": {
"undici": "^7.24.3" "tar": "^7.5.11",
"undici": "^7.24.3",
"flatted": "^3.4.2"
}
}, },
"devDependencies": { "devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0", "@kinvolk/headlamp-plugin": "^0.13.0",
@@ -43,11 +47,16 @@
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitest/coverage-v8": "^3.2.4",
"@headlamp-k8s/eslint-config": "^0.6.0",
"eslint": "^8.57.0",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"prettier": "^2.8.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"tar": "^7.5.11", "tar": "^7.5.11",
"typescript": "~5.6.2",
"undici": "^7.24.3", "undici": "^7.24.3",
"vitest": "^3.0.5" "vitest": "^3.0.5"
} }
+113 -30
View File
@@ -4,10 +4,18 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
tar: ^7.5.11
undici: ^7.24.3
flatted: ^3.4.2
importers: importers:
.: .:
devDependencies: devDependencies:
'@headlamp-k8s/eslint-config':
specifier: ^0.6.0
version: 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
'@kinvolk/headlamp-plugin': '@kinvolk/headlamp-plugin':
specifier: ^0.13.0 specifier: ^0.13.0
version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12)) version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
@@ -32,9 +40,18 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: ^19.2.3 specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.14) version: 19.2.3(@types/react@19.2.14)
'@vitest/coverage-v8':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))
eslint:
specifier: ^8.57.0
version: 8.57.1
jsdom: jsdom:
specifier: ^24.0.0 specifier: ^24.0.0
version: 24.1.3 version: 24.1.3
prettier:
specifier: ^2.8.8
version: 2.8.8
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@@ -44,6 +61,15 @@ importers:
react-router-dom: react-router-dom:
specifier: ^5.3.0 specifier: ^5.3.0
version: 5.3.4(react@18.3.1) version: 5.3.4(react@18.3.1)
tar:
specifier: ^7.5.11
version: 7.5.12
typescript:
specifier: ~5.6.2
version: 5.6.2
undici:
specifier: ^7.24.3
version: 7.24.5
vitest: vitest:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2) version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
@@ -53,6 +79,10 @@ packages:
'@adobe/css-tools@4.4.4': '@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
'@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
'@apidevtools/json-schema-ref-parser@11.7.2': '@apidevtools/json-schema-ref-parser@11.7.2':
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
@@ -159,6 +189,10 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@bundled-es-modules/cookie@2.0.1': '@bundled-es-modules/cookie@2.0.1':
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
@@ -1599,6 +1633,15 @@ packages:
peerDependencies: peerDependencies:
vitest: 3.2.4 vitest: 3.2.4
'@vitest/coverage-v8@3.2.4':
resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
peerDependencies:
'@vitest/browser': 3.2.4
vitest: 3.2.4
peerDependenciesMeta:
'@vitest/browser':
optional: true
'@vitest/expect@3.2.4': '@vitest/expect@3.2.4':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
@@ -1845,6 +1888,9 @@ packages:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'} engines: {node: '>=4'}
ast-v8-to-istanbul@0.3.12:
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
astral-regex@2.0.0: astral-regex@2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2824,8 +2870,8 @@ packages:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
flatted@3.4.0: flatted@3.4.2:
resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==} resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
for-each@0.3.5: for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
@@ -3390,6 +3436,9 @@ packages:
js-base64@3.7.8: js-base64@3.7.8:
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
js-tokens@10.0.0:
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -4727,8 +4776,8 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'} engines: {node: '>=6'}
tar@7.5.10: tar@7.5.12:
resolution: {integrity: sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==} resolution: {integrity: sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==}
engines: {node: '>=18'} engines: {node: '>=18'}
teex@1.0.1: teex@1.0.1:
@@ -4912,8 +4961,8 @@ packages:
undici-types@6.21.0: undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici@7.22.0: undici@7.24.5:
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==}
engines: {node: '>=20.18.1'} engines: {node: '>=20.18.1'}
unicorn-magic@0.1.0: unicorn-magic@0.1.0:
@@ -5312,6 +5361,11 @@ snapshots:
'@adobe/css-tools@4.4.4': {} '@adobe/css-tools@4.4.4': {}
'@ampproject/remapping@2.3.0':
dependencies:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
'@apidevtools/json-schema-ref-parser@11.7.2': '@apidevtools/json-schema-ref-parser@11.7.2':
dependencies: dependencies:
'@jsdevtools/ono': 7.1.3 '@jsdevtools/ono': 7.1.3
@@ -5455,6 +5509,8 @@ snapshots:
'@babel/helper-string-parser': 7.27.1 '@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5
'@bcoe/v8-coverage@1.0.2': {}
'@bundled-es-modules/cookie@2.0.1': '@bundled-es-modules/cookie@2.0.1':
dependencies: dependencies:
cookie: 0.7.2 cookie: 0.7.2
@@ -5546,7 +5602,7 @@ snapshots:
'@emotion/sheet@1.4.0': {} '@emotion/sheet@1.4.0': {}
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)': '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.28.6 '@babel/runtime': 7.28.6
'@emotion/babel-plugin': 11.13.5 '@emotion/babel-plugin': 11.13.5
@@ -5717,7 +5773,7 @@ snapshots:
js-yaml: 4.1.1 js-yaml: 4.1.1
semver: 7.7.4 semver: 7.7.4
table: 6.9.0 table: 6.9.0
tar: 7.5.10 tar: 7.5.12
tmp: 0.2.5 tmp: 0.2.5
yargs: 17.7.2 yargs: 17.7.2
@@ -5835,18 +5891,18 @@ snapshots:
dependencies: dependencies:
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@headlamp-k8s/eslint-config': 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) '@headlamp-k8s/eslint-config': 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
'@headlamp-k8s/pluginctl': 0.1.1 '@headlamp-k8s/pluginctl': 0.1.1
'@iconify/icons-mdi': 1.2.48 '@iconify/icons-mdi': 1.2.48
'@iconify/react': 3.2.2(react@18.3.1) '@iconify/react': 3.2.2(react@18.3.1)
'@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1) '@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)
'@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
'@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))) '@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
'@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))) '@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
@@ -5900,7 +5956,7 @@ snapshots:
jsdom: 24.1.3 jsdom: 24.1.3
jsonpath-plus: 10.4.0 jsonpath-plus: 10.4.0
lodash: 4.17.23 lodash: 4.17.23
material-react-table: 2.13.3(9c8771ba98ea800c4ce3ae047fad2581) material-react-table: 2.13.3(6e12a7d949eb369c0813bc8d1756414b)
monaco-editor: 0.52.2 monaco-editor: 0.52.2
msw: 2.4.9(typescript@5.6.2) msw: 2.4.9(typescript@5.6.2)
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2)) msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2))
@@ -5925,7 +5981,7 @@ snapshots:
spacetime: 7.12.0 spacetime: 7.12.0
storybook: 9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)) storybook: 9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))
table: 6.9.0 table: 6.9.0
tar: 7.5.10 tar: 7.5.12
ts-loader: 9.5.4(typescript@5.6.2)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12)) ts-loader: 9.5.4(typescript@5.6.2)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
typescript: 5.6.2 typescript: 5.6.2
validate-npm-package-name: 3.0.0 validate-npm-package-name: 3.0.0
@@ -6046,7 +6102,7 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.28.6 '@babel/runtime': 7.28.6
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -6060,7 +6116,7 @@ snapshots:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@types/react': 18.3.28 '@types/react': 18.3.28
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
@@ -6081,7 +6137,7 @@ snapshots:
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@types/react': 18.3.28 '@types/react': 18.3.28
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
@@ -6102,7 +6158,7 @@ snapshots:
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@types/react': 19.2.14 '@types/react': 19.2.14
'@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)': '@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)':
@@ -6133,7 +6189,7 @@ snapshots:
react: 18.3.1 react: 18.3.1
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)': '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
dependencies: dependencies:
@@ -6148,7 +6204,7 @@ snapshots:
react: 18.3.1 react: 18.3.1
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@types/react': 18.3.28 '@types/react': 18.3.28
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)': '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)':
@@ -6164,7 +6220,7 @@ snapshots:
react: 18.3.1 react: 18.3.1
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@types/react': 19.2.14 '@types/react': 19.2.14
'@mui/types@7.2.24(@types/react@18.3.28)': '@mui/types@7.2.24(@types/react@18.3.28)':
@@ -6244,7 +6300,7 @@ snapshots:
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies: optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
@@ -6256,11 +6312,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
'@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.28.6 '@babel/runtime': 7.28.6
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@mui/base': 5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/base': 5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
@@ -7149,6 +7205,25 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
ast-v8-to-istanbul: 0.3.12
debug: 4.4.3
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
istanbul-reports: 3.2.0
magic-string: 0.30.21
magicast: 0.3.5
std-env: 3.10.0
test-exclude: 7.0.2
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
transitivePeerDependencies:
- supports-color
'@vitest/expect@3.2.4': '@vitest/expect@3.2.4':
dependencies: dependencies:
'@types/chai': 5.2.3 '@types/chai': 5.2.3
@@ -7468,6 +7543,12 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
ast-v8-to-istanbul@0.3.12:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
estree-walker: 3.0.3
js-tokens: 10.0.0
astral-regex@2.0.0: {} astral-regex@2.0.0: {}
async-function@1.0.0: {} async-function@1.0.0: {}
@@ -7718,7 +7799,7 @@ snapshots:
parse5: 7.3.0 parse5: 7.3.0
parse5-htmlparser2-tree-adapter: 7.1.0 parse5-htmlparser2-tree-adapter: 7.1.0
parse5-parser-stream: 7.1.2 parse5-parser-stream: 7.1.2
undici: 7.22.0 undici: 7.24.5
whatwg-mimetype: 4.0.0 whatwg-mimetype: 4.0.0
chokidar@3.6.0: chokidar@3.6.0:
@@ -8632,11 +8713,11 @@ snapshots:
flat-cache@3.2.0: flat-cache@3.2.0:
dependencies: dependencies:
flatted: 3.4.0 flatted: 3.4.2
keyv: 4.5.4 keyv: 4.5.4
rimraf: 3.0.2 rimraf: 3.0.2
flatted@3.4.0: {} flatted@3.4.2: {}
for-each@0.3.5: for-each@0.3.5:
dependencies: dependencies:
@@ -9306,6 +9387,8 @@ snapshots:
js-base64@3.7.8: {} js-base64@3.7.8: {}
js-tokens@10.0.0: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@9.0.1: {} js-tokens@9.0.1: {}
@@ -9469,10 +9552,10 @@ snapshots:
'@types/minimatch': 3.0.5 '@types/minimatch': 3.0.5
minimatch: 3.1.5 minimatch: 3.1.5
material-react-table@2.13.3(9c8771ba98ea800c4ce3ae047fad2581): material-react-table@2.13.3(6e12a7d949eb369c0813bc8d1756414b):
dependencies: dependencies:
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1) '@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -10975,7 +11058,7 @@ snapshots:
tapable@2.3.0: {} tapable@2.3.0: {}
tar@7.5.10: tar@7.5.12:
dependencies: dependencies:
'@isaacs/fs-minipass': 4.0.1 '@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0 chownr: 3.0.0
@@ -11182,7 +11265,7 @@ snapshots:
undici-types@6.21.0: {} undici-types@6.21.0: {}
undici@7.22.0: {} undici@7.24.5: {}
unicorn-magic@0.1.0: {} unicorn-magic@0.1.0: {}
+2 -16
View File
@@ -1,19 +1,5 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"], "extends": ["github>privilegedescalation/.github:renovate-config"]
"baseBranches": ["main"],
"schedule": ["every weekend"],
"prConcurrentLimit": 10,
"packageRules": [
{
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "npm minor and patch"
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions minor and patch"
}
]
} }
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
# deploy-e2e-headlamp.sh
#
# Deploys a stock Headlamp instance with the polaris plugin loaded via
# a ConfigMap volume mount. No custom Docker images — the plugin is built
# in CI and injected as a ConfigMap.
#
# E2E resources are deployed to the `privilegedescalation-dev` namespace. Nothing
# persists beyond the test run — teardown cleans up all created resources.
#
# Prerequisites:
# - Plugin built (dist/ exists with plugin-main.js + package.json)
# - kubectl configured with cluster access
# - RBAC applied: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
#
# Environment:
# E2E_NAMESPACE — namespace for E2E Headlamp (default: privilegedescalation-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
# HEADLAMP_VERSION — Headlamp image tag (default: v0.40.1, pinned to match production)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST_DIR="$REPO_ROOT/dist"
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
HEADLAMP_VERSION="${HEADLAMP_VERSION:-v0.40.1}"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
exit 1
fi
# --- Preflight: verify RBAC before touching the cluster ---
echo "Checking RBAC permissions in namespace '${E2E_NAMESPACE}'..."
if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/null; then
echo "ERROR: Missing RBAC — cannot delete configmaps in namespace '${E2E_NAMESPACE}'." >&2
echo " Apply RBAC first: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml" >&2
exit 1
fi
echo "=== E2E Headlamp Deployment ==="
echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}"
echo " Namespace: $E2E_NAMESPACE"
echo " Release: $E2E_RELEASE"
# --- Create ConfigMap from built plugin ---
echo ""
echo "Creating ConfigMap with plugin files..."
# Delete existing ConfigMap if present (idempotent redeploy)
kubectl delete configmap headlamp-polaris-plugin \
-n "$E2E_NAMESPACE" --ignore-not-found
# Create ConfigMap from dist/ contents and package.json
kubectl create configmap headlamp-polaris-plugin \
-n "$E2E_NAMESPACE" \
--from-file="$DIST_DIR" \
--from-file=package.json="$REPO_ROOT/package.json"
# --- Tear down any existing E2E deployment for a clean start ---
# kubectl apply without prior deletion only patches in-place: if the pod spec is
# unchanged between runs, no new rollout is triggered and a degraded pod keeps
# serving. Delete first to guarantee a fresh pod regardless of prior state.
echo ""
echo "Removing any existing E2E deployment (clean-start)..."
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
# --- Deploy Headlamp via kubectl apply ---
echo ""
echo "Deploying Headlamp E2E instance..."
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
template:
metadata:
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
serviceAccountName: ${E2E_RELEASE}
automountServiceAccountToken: true
securityContext: {}
containers:
- name: headlamp
image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}
imagePullPolicy: IfNotPresent
securityContext:
runAsNonRoot: true
privileged: false
runAsUser: 100
runAsGroup: 101
args:
- "-in-cluster"
- "-in-cluster-context-name=main"
- "-plugins-dir=/headlamp/plugins"
ports:
- name: http
containerPort: 4466
protocol: TCP
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: polaris-plugin
mountPath: /headlamp/plugins/headlamp-polaris
readOnly: true
volumes:
- name: polaris-plugin
configMap:
name: headlamp-polaris-plugin
---
apiVersion: v1
kind: Service
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
EOF
echo "Waiting for rollout..."
kubectl rollout status "deployment/${E2E_RELEASE}" \
-n "$E2E_NAMESPACE" --timeout=120s
# --- Generate a service URL for tests ---
SVC_URL="http://${E2E_RELEASE}.${E2E_NAMESPACE}.svc.cluster.local"
# --- Wait for DNS and HTTP reachability ---
# rollout status only confirms the pod is ready per readinessProbe.
# Kubernetes Service DNS may still be propagating to the runner pod.
# Poll until the service is reachable over HTTP before handing off.
echo ""
echo "Waiting for ${SVC_URL} to be reachable..."
ATTEMPTS=0
MAX_ATTEMPTS=24 # 24 × 5s = 120s max
until curl -sf --max-time 5 "${SVC_URL}" -o /dev/null 2>/dev/null; do
ATTEMPTS=$((ATTEMPTS + 1))
if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then
echo "ERROR: ${SVC_URL} not reachable after $((MAX_ATTEMPTS * 5))s" >&2
exit 1
fi
echo " [${ATTEMPTS}/${MAX_ATTEMPTS}] not yet reachable, retrying in 5s..."
sleep 5
done
echo ""
echo "E2E Headlamp is ready at: ${SVC_URL}"
echo " export HEADLAMP_URL=${SVC_URL}"
# --- Generate a token for test auth ---
echo ""
echo "Creating service account token for E2E auth..."
kubectl create serviceaccount headlamp-e2e-test \
-n "$E2E_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "")
if [ -n "$TOKEN" ]; then
echo " export HEADLAMP_TOKEN=<generated>"
echo ""
echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e"
echo "HEADLAMP_TOKEN=${TOKEN}" >> "$REPO_ROOT/.env.e2e"
echo "Wrote .env.e2e with HEADLAMP_URL and HEADLAMP_TOKEN"
else
echo " WARNING: Could not generate token. Set HEADLAMP_TOKEN manually or use OIDC."
fi
echo ""
echo "E2E deployment complete."
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env bash
# deploy-plugin-via-volume.sh
#
# Copies the built plugin into the shared PVC so Headlamp picks it up.
# Uses a temporary Kubernetes Job to write to the PVC — the CI runner
# does NOT need the PVC mounted locally.
#
# Usage:
# scripts/deploy-plugin-via-volume.sh
#
# Environment:
# HEADLAMP_NAMESPACE — namespace where Headlamp runs (default: kube-system)
# HEADLAMP_DEPLOY — Headlamp deployment name (default: headlamp)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
HEADLAMP_NAMESPACE="${HEADLAMP_NAMESPACE:-kube-system}"
HEADLAMP_DEPLOY="${HEADLAMP_DEPLOY:-headlamp}"
# The deployed directory name must match the package.json name and
# the registerPluginSettings name. Headlamp identifies plugins by
# reading package.json from each subdirectory of the plugins dir.
PLUGIN_DIR_NAME="headlamp-polaris"
DIST_DIR="$REPO_ROOT/dist"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
exit 1
fi
echo "Deploying plugin to shared volume via temporary job..."
echo " Source: $DIST_DIR"
echo " PVC: headlamp-plugins"
echo " Plugin: $PLUGIN_DIR_NAME"
# Create tarball of plugin dist + package.json
TAR_FILE=$(mktemp /tmp/plugin-XXXXXX.tar.gz)
tar -czf "$TAR_FILE" -C "$DIST_DIR" . -C "$REPO_ROOT" package.json
echo " Tarball: $TAR_FILE ($(du -h "$TAR_FILE" | cut -f1))"
# Find the node where Headlamp is running — the PVC is ReadWriteOnce so
# the deploy job must land on the same node to mount it.
HEADLAMP_NODE=$(kubectl get pods -n "$HEADLAMP_NAMESPACE" \
-l "app.kubernetes.io/name=headlamp" \
-o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || true)
if [ -z "$HEADLAMP_NODE" ]; then
HEADLAMP_NODE=$(kubectl get pods -n "$HEADLAMP_NAMESPACE" \
-l "app.kubernetes.io/instance=headlamp" \
-o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || true)
fi
if [ -n "$HEADLAMP_NODE" ]; then
echo " Headlamp node: $HEADLAMP_NODE (scheduling deploy job there)"
fi
# Clean up any previous deploy resources
kubectl delete pod plugin-deploy -n "$HEADLAMP_NAMESPACE" --ignore-not-found --wait=true 2>/dev/null || true
kubectl delete configmap plugin-tarball -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
sleep 2
# Store the tarball in a ConfigMap (binary-safe via --from-file)
echo "Creating ConfigMap with plugin tarball..."
kubectl create configmap plugin-tarball \
-n "$HEADLAMP_NAMESPACE" \
--from-file=plugin.tar.gz="$TAR_FILE"
# Build the Pod manifest as a temp file to avoid heredoc YAML escaping issues
POD_FILE=$(mktemp /tmp/plugin-deploy-pod-XXXXXX.yaml)
cat > "$POD_FILE" <<'YAMLDOC'
apiVersion: v1
kind: Pod
metadata:
name: plugin-deploy
spec:
restartPolicy: Never
containers:
- name: deploy
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
echo "Cleaning up stale plugin directories..."
rm -rf /plugins/polaris /plugins/headlamp-polaris
echo "Extracting plugin to shared volume..."
mkdir -p /plugins/PLUGIN_DIR_PLACEHOLDER
tar -xzf /tarball/plugin.tar.gz -C /plugins/PLUGIN_DIR_PLACEHOLDER
echo "Files deployed:"
ls -la /plugins/PLUGIN_DIR_PLACEHOLDER/
volumeMounts:
- name: plugins
mountPath: /plugins
- name: tarball
mountPath: /tarball
readOnly: true
volumes:
- name: plugins
persistentVolumeClaim:
claimName: headlamp-plugins
- name: tarball
configMap:
name: plugin-tarball
YAMLDOC
# Substitute plugin dir name
sed -i "s/PLUGIN_DIR_PLACEHOLDER/${PLUGIN_DIR_NAME}/g" "$POD_FILE"
# Add nodeName if we know which node Headlamp is on
if [ -n "$HEADLAMP_NODE" ]; then
sed -i "/restartPolicy: Never/i\\ nodeName: ${HEADLAMP_NODE}" "$POD_FILE"
fi
echo "Starting deploy pod..."
kubectl apply -n "$HEADLAMP_NAMESPACE" -f "$POD_FILE"
rm -f "$POD_FILE"
# Wait for the pod to complete (Succeeded phase)
echo "Waiting for deploy pod to complete..."
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/plugin-deploy \
-n "$HEADLAMP_NAMESPACE" --timeout=120s
# Show logs
kubectl logs plugin-deploy -n "$HEADLAMP_NAMESPACE" 2>/dev/null || true
# Clean up
kubectl delete pod plugin-deploy -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
kubectl delete configmap plugin-tarball -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
rm -f "$TAR_FILE"
# Restart Headlamp to pick up the new plugin
echo "Restarting Headlamp deployment to load plugin..."
kubectl rollout restart "deployment/$HEADLAMP_DEPLOY" -n "$HEADLAMP_NAMESPACE"
kubectl rollout status "deployment/$HEADLAMP_DEPLOY" -n "$HEADLAMP_NAMESPACE" --timeout=120s
echo "Plugin deployed successfully."
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# teardown-e2e-headlamp.sh
#
# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh.
#
# Environment:
# E2E_NAMESPACE — namespace to clean up (default: privilegedescalation-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
echo "=== E2E Headlamp Teardown ==="
echo " Namespace: $E2E_NAMESPACE"
echo " Release: $E2E_RELEASE"
echo "Removing Headlamp Deployment, Service, and ServiceAccount..."
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up ConfigMap..."
kubectl delete configmap headlamp-polaris-plugin -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up test service account..."
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found
# Clean up local env file
rm -f "$REPO_ROOT/.env.e2e"
echo "Teardown complete."
+432
View File
@@ -0,0 +1,432 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { makeResult } from '../test-utils';
const { mockApiRequest } = vi.hoisted(() => ({ mockApiRequest: vi.fn() }));
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
ApiProxy: { request: mockApiRequest },
}));
vi.mock('@mui/material/styles', () => ({
useTheme: () => ({
palette: {
primary: { main: '#1976d2', contrastText: '#fff' },
action: { disabledBackground: '#e0e0e0', disabled: '#9e9e9e' },
divider: '#e0e0e0',
},
}),
}));
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
<div data-testid="section-box" data-title={title}>
{children}
</div>
),
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
<span data-testid="status-label" data-status={status}>
{children}
</span>
),
Dialog: ({
open,
children,
title,
}: {
open: boolean;
onClose?: () => void;
title?: string;
children?: React.ReactNode;
}) =>
open ? (
<div data-testid="dialog" data-title={title}>
{children}
</div>
) : null,
}));
import ExemptionManager from './ExemptionManager';
const defaultProps = {
workloadResult: makeResult(),
namespace: 'default',
kind: 'Deployment',
name: 'my-deploy',
};
const resultWithPodFailures = makeResult({
PodResult: {
Name: 'pod',
Results: {
hostIPCSet: {
ID: 'hostIPCSet',
Message: 'Host IPC is set',
Details: [],
Success: false,
Severity: 'danger',
Category: 'Security',
},
hostPIDSet: {
ID: 'hostPIDSet',
Message: 'Host PID is set',
Details: [],
Success: false,
Severity: 'danger',
Category: 'Security',
},
},
ContainerResults: [],
},
});
const resultWithContainerFailures = makeResult({
PodResult: {
Name: 'pod',
Results: {},
ContainerResults: [
{
Name: 'container-1',
Results: {
cpuRequestsMissing: {
ID: 'cpuRequestsMissing',
Message: 'CPU requests missing',
Details: [],
Success: false,
Severity: 'warning',
Category: 'Efficiency',
},
},
},
],
},
});
const resultWithIgnoredFailures = makeResult({
PodResult: {
Name: 'pod',
Results: {
hostIPCSet: {
ID: 'hostIPCSet',
Message: '',
Details: [],
Success: false,
Severity: 'ignore',
Category: 'Security',
},
},
ContainerResults: [],
},
});
describe('ExemptionManager', () => {
describe('rendering failing checks', () => {
it('shows disabled Add Exemption button when no failing checks', () => {
render(<ExemptionManager {...defaultProps} />);
const btn = screen.getByRole('button', { name: /add exemption/i });
expect(btn).toBeDisabled();
});
it('shows enabled Add Exemption button when there are failing checks', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
const btn = screen.getByRole('button', { name: /add exemption/i });
expect(btn).not.toBeDisabled();
});
it('does not include ignored-severity checks as failing', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithIgnoredFailures} />);
const btn = screen.getByRole('button', { name: /add exemption/i });
expect(btn).toBeDisabled();
});
it('collects failing checks from pod-level results', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByText('Host IPC')).toBeInTheDocument();
expect(screen.getByText('Host PID')).toBeInTheDocument();
});
it('collects failing checks from container-level results', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithContainerFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByText('CPU Requests')).toBeInTheDocument();
});
it('deduplicates checks that appear in multiple containers', () => {
const resultWithDuplicate = makeResult({
PodResult: {
Name: 'pod',
Results: {},
ContainerResults: [
{
Name: 'container-1',
Results: {
cpuRequestsMissing: {
ID: 'cpuRequestsMissing',
Message: '',
Details: [],
Success: false,
Severity: 'warning',
Category: 'Efficiency',
},
},
},
{
Name: 'container-2',
Results: {
cpuRequestsMissing: {
ID: 'cpuRequestsMissing',
Message: '',
Details: [],
Success: false,
Severity: 'warning',
Category: 'Efficiency',
},
},
},
],
},
});
render(<ExemptionManager {...defaultProps} workloadResult={resultWithDuplicate} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
const items = screen.getAllByText('CPU Requests');
expect(items).toHaveLength(1);
});
});
describe('dialog interactions', () => {
it('opens dialog when Add Exemption button is clicked', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByTestId('dialog')).toBeInTheDocument();
});
it('closes dialog when Cancel button is clicked', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByTestId('dialog')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
});
it('toggles individual check selection', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
// Find the checkbox next to "Host IPC"
const checkboxes = screen.getAllByRole('checkbox');
// First checkbox is "Exempt from all checks", rest are individual checks
const hostIPCCheckbox = checkboxes[1];
expect(hostIPCCheckbox).not.toBeChecked();
fireEvent.click(hostIPCCheckbox);
expect(hostIPCCheckbox).toBeChecked();
fireEvent.click(hostIPCCheckbox);
expect(hostIPCCheckbox).not.toBeChecked();
});
it('hides individual checks list when exempt-all is toggled', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByText('Host IPC')).toBeInTheDocument();
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
fireEvent.click(exemptAllCheckbox);
expect(screen.queryByText('Host IPC')).not.toBeInTheDocument();
});
it('Apply button is disabled when no checks selected and exemptAll is false', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
expect(screen.getByRole('button', { name: /apply/i })).toBeDisabled();
});
it('Apply button is enabled when exemptAll is checked', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
fireEvent.click(exemptAllCheckbox);
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
});
it('Apply button is enabled when at least one individual check is selected', () => {
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
const checkboxes = screen.getAllByRole('checkbox');
fireEvent.click(checkboxes[1]); // select first individual check
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
});
});
describe('ApiProxy.request calls', () => {
it('patches with exempt-all annotation when exemptAll is selected', async () => {
mockApiRequest.mockResolvedValue({});
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
expect.objectContaining({
method: 'PATCH',
headers: { 'Content-Type': 'application/strategic-merge-patch+json' },
body: JSON.stringify({
metadata: {
annotations: { 'polaris.fairwinds.com/exempt': 'true' },
},
}),
})
);
});
});
it('patches with per-check annotations when individual checks selected', async () => {
mockApiRequest.mockResolvedValue({});
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
// Select first check (hostIPCSet)
fireEvent.click(screen.getAllByRole('checkbox')[1]);
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
expect.objectContaining({
method: 'PATCH',
body: JSON.stringify({
metadata: {
annotations: { 'polaris.fairwinds.com/hostIPCSet-exempt': 'true' },
},
}),
})
);
});
});
it('uses core API path for Pod kind (no api group)', async () => {
mockApiRequest.mockResolvedValue({});
render(
<ExemptionManager {...defaultProps} kind="Pod" workloadResult={resultWithPodFailures} />
);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/api/v1/namespaces/default/pods/my-deploy',
expect.anything()
);
});
});
it('uses batch API group for Job kind', async () => {
mockApiRequest.mockResolvedValue({});
render(
<ExemptionManager {...defaultProps} kind="Job" workloadResult={resultWithPodFailures} />
);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/apis/batch/v1/namespaces/default/jobs/my-deploy',
expect.anything()
);
});
});
it('uses batch API group for CronJob kind', async () => {
mockApiRequest.mockResolvedValue({});
render(
<ExemptionManager {...defaultProps} kind="CronJob" workloadResult={resultWithPodFailures} />
);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/apis/batch/v1/namespaces/default/cronjobs/my-deploy',
expect.anything()
);
});
});
it('uses apps API group for StatefulSet kind', async () => {
mockApiRequest.mockResolvedValue({});
render(
<ExemptionManager
{...defaultProps}
kind="StatefulSet"
workloadResult={resultWithPodFailures}
/>
);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(mockApiRequest).toHaveBeenCalledWith(
'/apis/apps/v1/namespaces/default/statefulsets/my-deploy',
expect.anything()
);
});
});
});
describe('feedback states', () => {
it('shows success feedback and closes dialog after successful apply', async () => {
mockApiRequest.mockResolvedValue({});
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
const label = screen.getByTestId('status-label');
expect(label).toHaveAttribute('data-status', 'success');
expect(label).toHaveTextContent('Exemptions applied successfully');
});
});
it('shows error feedback and keeps dialog closed after failed apply', async () => {
mockApiRequest.mockRejectedValue(new Error('403 Forbidden'));
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
await waitFor(() => {
const label = screen.getByTestId('status-label');
expect(label).toHaveAttribute('data-status', 'error');
expect(label).toHaveTextContent(/failed to apply exemptions/i);
});
});
it('shows "Applying..." text on Apply button while in-flight', async () => {
let resolveRequest!: () => void;
mockApiRequest.mockReturnValue(
new Promise<void>(res => {
resolveRequest = res;
})
);
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
expect(screen.getByRole('button', { name: /applying/i })).toBeInTheDocument();
resolveRequest();
await waitFor(() => {
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
});
});
});
});
+11
View File
@@ -9,5 +9,16 @@ export default defineConfig({
environment: 'jsdom', environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'], setupFiles: ['./vitest.setup.ts'],
exclude: ['e2e/**', 'node_modules/**'], exclude: ['e2e/**', 'node_modules/**'],
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/index.tsx'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
}, },
}); });