Compare commits

..

34 Commits

Author SHA1 Message Date
Chris Farhood 7d56a903c8 fix(e2e): add waitForSidebar helper and networkidle waits for reliability
Add waitForSidebar helper function with explicit sidebar visibility wait
and networkidle state to ensure page is fully loaded before assertions.
This addresses flaky E2E tests where elements were not consistently
found due to timing issues during page transitions.
2026-05-05 06:50:21 +00:00
Chris Farhood ef7183a2d6 Fix E2E workflow: use pnpm-capable reusable workflow branch
The reusable plugin-e2e.yaml@main lacks pnpm support. Switching to
the PR branch that has pnpm detector, Corepack setup, and pnpm commands.

Will revert to @main once PR #141 merges.

- PRI-619 E2E fix

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 06:10:20 +00:00
Chris Farhood 6d48470691 Add E2E test infrastructure for tns-csi plugin
Scaffolded via e2e-scaffold.sh (proactive improvement).
- playwright.config.ts, e2e/auth.setup.ts, e2e/tns-csi.spec.ts
- scripts/deploy-e2e-headlamp.sh, scripts/teardown-e2e-headlamp.sh
- .github/workflows/e2e.yaml uses reusable workflow
- @playwright/test ^1.58.2 devDep

- PRI-639

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 05:14:51 +00:00
privilegedescalation-engineer[bot] 4ba90fa218 chore: replace Dependabot reference with Renovate (#31)
- SECURITY.md: update to mention Renovate (org-wide Mend Renovate)

Closes PRI-389. Parent PRI-387.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 21:19:18 +00:00
privilegedescalation-engineer[bot] a089a2cc2d fix: add markdownlint config to resolve CI failures (#30)
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 20:02:47 +00:00
privilegedescalation-engineer[bot] bb283d8923 fix: override lodash >=4.18.0 to patch code injection vulnerability (#29)
* Regenerate lockfile for lodash override

- Explicitly add lodash@4.18.1 to ensure override is respected
- Regenerated pnpm-lock.yaml with resolved lodash@4.18.1 (CVE fix)

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

* fix: drop bogus direct lodash devDependency that conflicted with override

The rebase added "lodash": "4.18.1" as a direct devDependency alongside
the >=4.18.0 override, which npm rejects with EOVERRIDE during the
headlamp-plugin build step. The plugin source does not import lodash;
the override alone is sufficient to patch the transitive CVE.

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

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 03:23:57 +00:00
privilegedescalation-engineer[bot] 0af2f24a27 fix: update vite to >=6.4.2 to patch arbitrary file read vulnerability (#28)
Vite versions >=6.0.0 <=6.4.1 are vulnerable to arbitrary file read via
the Vite Dev Server WebSocket (server.fs.deny bypass with queries).

CVE: GHSA-p9ff-h696-f583

Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 17:44:12 +00:00
privilegedescalation-engineer[bot] 409efe84d5 fix: pass pr_number to dual-approval-check workflow (#27)
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:22 +00:00
privilegedescalation-ceo[bot] a5032b23d1 Merge pull request #25 from privilegedescalation/fix/add-package-manager-field
fix: add packageManager field to package.json
2026-03-24 22:45:34 +00:00
privilegedescalation-engineer[bot] c241b8d9d5 release: v1.0.0 (#24)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-24 22:31:55 +00:00
Gandalf the Greybeard 7ae5efda73 fix: add packageManager field to package.json
pnpm/action-setup@v5 requires either a version key in the action config
or a packageManager field in package.json. Add the field to unblock the
release workflow.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 22:12:38 +00:00
privilegedescalation-ceo[bot] fd1d76c932 Merge pull request #19 from privilegedescalation/release/v1.0.0
release: tns-csi v1.0.0
2026-03-24 22:01:22 +00:00
Gandalf the Greybeard dc981feaa4 fix(ci): add missing eslint/prettier/typescript devDeps, fix tsconfig types
Add eslint@^8.57.0, @headlamp-k8s/eslint-config@^0.6.0, prettier@^2.8.8,
typescript@~5.6.2 as explicit devDependencies. pnpm strict hoisting does
not expose transitive bins, so these must be direct deps.

Remove vite/client and vite-plugin-svgr/client from tsconfig types; these
are transitive deps pnpm does not hoist and polaris plugin omits them.
2026-03-24 21:49:17 +00:00
Gandalf the Greybeard 77586a98eb release: prepare v1.0.0
- Bump version from 0.2.7 to 1.0.0 in package.json
- Add missing devDependencies: @mui/material, @types/react, @types/react-dom,
  notistack; upgrade vitest to ^3.2.4 (matching reference polaris plugin)
- Fix vitest.config.mts: add define block for process.env.NODE_ENV="test"
  to resolve act() errors in all 159 component tests
- Remove package-lock.json; adopt pnpm-lock.yaml as canonical lock file
- Update artifacthub-pkg.yml: version 1.0.0, new archive URL, TBD checksum,
  updated changes block describing this release
- Update CHANGELOG.md: add [1.0.0] - 2026-03-24 entry documenting test
  infrastructure fixes, dependency additions, post-0.2.7 CI/workflow changes;
  update version comparison links

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:29:47 +00:00
privilegedescalation-ceo[bot] bfe95475c6 Merge pull request #18 from privilegedescalation/feat/renovate-extend-org-config
feat: extend Renovate config from org-level preset
2026-03-24 18:46:11 +00:00
Hugh Hackman f69dfd6356 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:22 +00:00
privilegedescalation-ceo[bot] 3c5a837a9d Merge pull request #17 from privilegedescalation/chore/renovate-pin-digests
chore(renovate): add pinDigests for GitHub Actions SHA pinning
2026-03-22 11:10:58 +00:00
privilegedescalation-engineer[bot] f4e4e24b6c 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:11 +00:00
privilegedescalation-ceo[bot] fef2c3c3e5 Merge pull request #16 from privilegedescalation/feat/dual-approval-status-check
ci: add dual-approval status check (CTO + QA)
2026-03-22 04:12:31 +00:00
privilegedescalation-engineer[bot] 423282ec6c ci: add dual-approval caller workflow
Calls the shared privilegedescalation/.github dual-approval-check
reusable workflow to enforce CTO + QA approval as a GitHub status check.

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

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:55:56 +00:00
privilegedescalation-paperclip[bot] 4ae7aa6a91 ci: pass GitHub App token secrets to release workflow (#15)
The shared release workflow now requires RELEASE_APP_ID and
RELEASE_APP_PRIVATE_KEY secrets for PR creation, since the org
blocks GITHUB_TOKEN from creating PRs.

Depends on privilegedescalation/.github#31

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

Co-authored-by: Hugh Hackman [bot] <hugh-hackman[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:32:59 +00:00
null-pointer-nancy[bot] bc59cd7a23 Merge pull request #12 from privilegedescalation/fix/add-missing-devdependencies
fix: add missing devDependencies for CI
2026-03-18 23:43:52 +00:00
Gandalf the Greybeard aa9a0d38fe fix: add missing devDependencies for CI (vitest, testing-library, jsdom, react)
The package.json only listed @kinvolk/headlamp-plugin as a devDependency,
but CI runs tsc, eslint, prettier, and vitest which all require additional
packages. Add the same devDependencies used by the reference kube-vip plugin
and regenerate the lock file.

Also adds peerDependencies for react/react-dom to match the reference plugin
conventions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 23:26:41 +00:00
null-pointer-nancy[bot] 2c2ad720e5 Merge pull request #11 from privilegedescalation/fix/dep-security-overrides-tar-undici
fix: add npm overrides for tar and undici security advisories
2026-03-18 23:14:05 +00:00
Hugh Hackman dc6dee9d4d fix: regenerate package-lock.json for undici override
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 23:08:02 +00:00
Hugh Hackman 5e93973fa7 fix: add npm overrides for tar and undici security advisories
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 22:55:36 +00:00
null-pointer-nancy[bot] 93b5018f60 Merge pull request #9 from privilegedescalation/docs/remove-manual-install
docs: remove manual install sections from README
2026-03-17 12:19:34 +00:00
Gandalf the Greybeard 1b2a6046cd docs: remove manual install sections from README
ArtifactHub plugin installer is the only supported installation method.
Remove manual tarball, sidecar, and build-from-source install options
to align documentation with company policy.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 12:15:46 +00:00
null-pointer-nancy[bot] ba5c296a13 ci: retrigger after shared workflow fix (#8)
CI retrigger after shared workflow fix (.github PR#14)
2026-03-15 17:54:36 +00:00
Chris Farhood 2d92bce571 Merge pull request #7 from privilegedescalation/policy/artifacthub-only
policy: add ArtifactHub-only installation requirement
2026-03-15 12:43:06 -04:00
null-pointer-nancy[bot] 8fb4c18e8a policy: add ArtifactHub-only installation policy
Per CEO directive, ArtifactHub via the Headlamp plugin installer is the
only approved installation method. No exceptions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-15 16:36:43 +00:00
22 changed files with 12579 additions and 18251 deletions
+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 }}
+23
View File
@@ -0,0 +1,23 @@
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: e2e-${{ github.repository }}
cancel-in-progress: false
jobs:
e2e:
uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e
with:
node-version: '22'
headlamp-version: v0.40.1
e2e-namespace: headlamp-dev
+4
View File
@@ -10,10 +10,14 @@ on:
permissions:
contents: write
pull-requests: write
jobs:
release:
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
version: ${{ inputs.version }}
upstream-repo: fenio/tns-csi
+6
View File
@@ -5,3 +5,9 @@ dist/
.env
.env.local
.eslintcache
# E2E
e2e/.auth/
.env.e2e
playwright-report/
test-results/
+53
View File
@@ -0,0 +1,53 @@
{
"config": {
// Line length — not enforced for docs with code examples
"MD013": false,
// First line heading — files use YAML frontmatter, not headings
"MD041": false,
// Emphasis as heading — common pattern for Option 1/2/3 sections
"MD036": false,
// No duplicate heading — changelog files repeat section names intentionally
"MD024": false,
// Fenced code language — not always applicable for diagram blocks
"MD040": false,
// Table column style — table alignment is visual, not semantic
"MD060": false,
// Ordered list item prefix — number resets are intentional in documents
"MD029": false,
// No inline HTML — each elements are valid in valid Markdown
"MD033": false,
// List marker space — spacing after list markers varies by editor
"MD030": false,
// Blanks around headings — not always needed in compact docs
"MD022": false,
// Blanks around lists — not always needed in compact docs
"MD032": false,
// Blanks around fences — not always needed between adjacent blocks
"MD031": false,
// Multiple blanks — editor artifacts, not semantic
"MD012": false,
// Single title — files may have multiple H1 sections
"MD025": false,
// Trailing spaces — editor artifacts
"MD009": false,
// Bare URLs — URL shortening not always needed
"MD034": false,
// Single trailing newline — editor artifacts
"MD047": false,
// Trailing punctuation — heading punctuation is intentional
"MD026": false,
// Space in emphasis — double-asterisk bold spacing varies by renderer
"MD037": false,
// No hard tabs — some generated docs use tabs for indentation
"MD010": false,
// Code block style — generated docs may use inconsistent styles
"MD046": false,
// Comment style — generated docs have no comments
"MD048": false,
// Commands show output — shell examples intentionally show only commands
"MD014": false
},
"ignores": [
"docs/api-reference/generated/**"
]
}
+1
View File
@@ -0,0 +1 @@
docs/api-reference/generated/**
+18 -1
View File
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.0.0] - 2026-03-24
### Added
- **Missing devDependencies** — added `@mui/material`, `@types/react`, `@types/react-dom`, and `notistack` to devDependencies so the full test suite resolves correctly; upgraded `vitest` to `^3.2.4`
### Changed
- **Test infrastructure fix** — added `define: { 'process.env.NODE_ENV': '"test"' }` to `vitest.config.mts` to prevent React production-build `act()` errors; all 159 component tests now pass
- **Version bump** — bumped package version from 0.2.7 to 1.0.0 (stable release)
- **Lock file** — removed `package-lock.json`; `pnpm-lock.yaml` is now the canonical lock file
- **Renovate config** — extended from org-level preset (PR #18)
- **GitHub Actions SHA pinning** — added `pinDigests` to Renovate config for SHA-pinned Actions (PR #17)
- **Dual-approval caller workflow** — added dual-approval status check workflow to CI (PR #16)
- **Release workflow** — GitHub App token secrets now passed to release workflow (PR #15)
## [0.2.6] - 2026-03-04
### Fixed
@@ -102,7 +118,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TypeScript strict mode with zero `any` types
- ESLint + Prettier code quality tooling
[Unreleased]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v0.2.6...HEAD
[Unreleased]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v0.2.7...v1.0.0
[0.2.6]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v0.2.5...v0.2.6
[0.2.4]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/compare/v0.2.2...v0.2.3
+24
View File
@@ -0,0 +1,24 @@
# Installation Policy
## Approved Installation Method
**The ONLY approved method for installing this plugin is via [Artifact Hub](https://artifacthub.io/) using the Headlamp plugin installer.**
No other installation method is acceptable. This includes but is not limited to:
- Direct installation from GitHub release assets
- Manual npm pack / tarball extraction
- initContainer workarounds that bypass Artifact Hub
- Direct file copy or sidecar injection
## Enforcement
All deployment configurations, CI/CD pipelines, and documentation MUST reference Artifact Hub as the sole plugin distribution channel. Any pull request that introduces an alternative installation method will be rejected.
## Rationale
Artifact Hub provides verified checksums, consistent versioning, and a standard discovery mechanism for the CNCF ecosystem. Bypassing it introduces security and integrity risks.
---
*This policy is set by the CTO and approved by the CEO of Privileged Escalation.*
+7 -28
View File
@@ -47,9 +47,14 @@ The tns-csi driver must be deployed in `kube-system` with the standard `app.kube
## Installing
### Option 1: Headlamp Plugin Manager (Recommended)
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/tns-csi/headlamp-tns-csi-plugin). Install via the Headlamp UI:
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/tns-csi/headlamp-tns-csi-plugin). Configure Headlamp via Helm:
1. Go to **Settings → Plugins**
2. Click **Catalog** tab
3. Search for "TNS CSI" or "TrueNAS"
4. Click **Install**
Or configure Headlamp via Helm:
```yaml
config:
@@ -61,32 +66,6 @@ pluginsManager:
url: https://github.com/privilegedescalation/headlamp-tns-csi-plugin/releases/download/v0.2.4/tns-csi-0.2.4.tar.gz
```
Or install via the Headlamp UI:
1. Go to **Settings → Plugins**
2. Click **Catalog** tab
3. Search for "TNS CSI" or "TrueNAS"
4. Click **Install**
### Option 2: Manual Tarball Install
Download the `.tar.gz` from the [GitHub releases page](https://github.com/privilegedescalation/headlamp-tns-csi-plugin/releases), then extract into Headlamp's plugin directory:
```bash
wget https://github.com/privilegedescalation/headlamp-tns-csi-plugin/releases/download/v0.2.4/tns-csi-0.2.4.tar.gz
tar xzf tns-csi-0.2.4.tar.gz -C /headlamp/plugins/
```
### Option 3: Build from Source
```bash
git clone https://github.com/privilegedescalation/headlamp-tns-csi-plugin.git
cd headlamp-tns-csi-plugin
npm install
npm run build
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
```
## RBAC / Security Setup
The plugin reads from the Kubernetes API and the tns-csi controller pod's Prometheus endpoint. The Benchmark page additionally creates and deletes Jobs and PVCs.
+1 -1
View File
@@ -187,7 +187,7 @@ Report security vulnerabilities via:
The project uses:
- **npm audit**: Runs automatically during `npm install`
- **GitHub Dependabot**: Monitors dependencies and creates PRs for updates
- **Renovate**: Automated dependency updates via Mend Renovate (org-wide configured)
Headlamp itself (`@kinvolk/headlamp-plugin`) is a peer dependency. Security updates to Headlamp should be applied by upgrading your Headlamp installation.
+13 -13
View File
@@ -1,4 +1,4 @@
version: "0.2.6"
version: "1.0.0"
name: headlamp-tns-csi-plugin
displayName: TrueNAS CSI (tns-csi)
description: >-
@@ -13,7 +13,7 @@ license: Apache-2.0
category: storage
homeURL: https://github.com/privilegedescalation/headlamp-tns-csi-plugin
appVersion: "0.17.0"
appVersion: "0.17.4"
keywords:
- headlamp
@@ -48,22 +48,22 @@ links:
changes:
- kind: added
description: "Component tests for all 8 UI components"
description: "Stable v1.0.0 release marking production readiness"
- kind: added
description: "Missing devDependencies for test infrastructure (@mui/material, @types/react, @types/react-dom, notistack, vitest upgraded to ^3.2.4)"
- kind: changed
description: "CI workflow split into parallel lint, typecheck, test jobs with build gating on all three"
description: "vitest.config.mts: added define block to set process.env.NODE_ENV=test, fixing act() errors in all component tests"
- kind: changed
description: "JUnit test reporter for PR test result visibility"
description: "Renovate config extended from org-level preset"
- kind: changed
description: "Node.js 20 upgraded to 22 (current LTS) in all workflows"
description: "GitHub Actions SHA pinning via pinDigests in Renovate"
- kind: changed
description: "Release workflow gated on CI passing with concurrency protection"
- kind: fixed
description: "Conditional React hook call in OverviewPage"
- kind: fixed
description: "appVersion now tracks upstream tns-csi driver release (0.12.0)"
description: "Dual-approval caller workflow added to CI"
- kind: changed
description: "GitHub App token secrets passed to release workflow"
annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-tns-csi-plugin/releases/download/v0.2.6/tns-csi-0.2.6.tar.gz"
headlamp/plugin/archive-checksum: sha256:c7ab23e88c7182df06c4b15e59be055d9dfc85159266986ae9d79d186b62aa3c
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-tns-csi-plugin/releases/download/v1.0.0/tns-csi-1.0.0.tar.gz"
headlamp/plugin/archive-checksum: sha256:e38846ceb17a79438f8aecc50f22920b0efa7456f3ebb3e628d89856af83ad01
headlamp/plugin/version-compat: ">=0.20.0"
headlamp/plugin/distro-compat: "in-cluster,web,app"
+69
View File
@@ -0,0 +1,69 @@
import { test as setup, expect, Page } from '@playwright/test';
const AUTH_STATE_PATH = 'e2e/.auth/state.json';
async function authenticateWithOIDC(page: Page, username: string, password: string): Promise<void> {
await page.goto('/');
await page.waitForURL('**/login');
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: /sign in/i }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
await popup.waitForLoadState('networkidle');
const usernameField = popup.getByRole('textbox', { name: /email or username/i });
await usernameField.waitFor({ state: 'visible', timeout: 15_000 });
await usernameField.fill(username);
await popup.getByRole('button', { name: /log in/i }).click();
await popup.waitForLoadState('networkidle');
const passwordField = popup.getByRole('textbox', { name: /password/i });
await passwordField.waitFor({ state: 'visible', timeout: 15_000 });
await passwordField.fill(password);
await popup.getByRole('button', { name: /continue|log in/i }).click();
await popup.waitForEvent('close', { timeout: 15_000 });
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({
timeout: 15_000,
});
}
async function authenticateWithToken(page: Page, token: string): Promise<void> {
await page.goto('/');
await page.waitForURL(/\/(login|token)$/);
if (page.url().includes('/login')) {
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');
}
await page.getByRole('textbox', { name: /id token/i }).fill(token);
await page.getByRole('button', { name: /authenticate/i }).click();
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({
timeout: 15_000,
});
}
setup('authenticate with Headlamp', async ({ page }) => {
const username = process.env.AUTHENTIK_USERNAME;
const password = process.env.AUTHENTIK_PASSWORD;
const token = process.env.HEADLAMP_TOKEN;
if (username && password) {
await authenticateWithOIDC(page, username, password);
} else if (token) {
await authenticateWithToken(page, token);
} else {
throw new Error(
'Set AUTHENTIK_USERNAME + AUTHENTIK_PASSWORD for OIDC auth, or HEADLAMP_TOKEN for token auth'
);
}
await page.context().storageState({ path: AUTH_STATE_PATH });
});
+50
View File
@@ -0,0 +1,50 @@
import { test, expect } from '@playwright/test';
async function waitForSidebar(page: import('@playwright/test').Page) {
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
await page.waitForLoadState('networkidle');
return sidebar;
}
test.describe('TNS CSI plugin smoke tests', () => {
test('sidebar contains TNS CSI entry', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
await expect(sidebar.getByRole('button', { name: /tns.csi/i })).toBeVisible();
});
test('TNS CSI sidebar entry navigates to TNS CSI view', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
const entry = sidebar.getByRole('button', { name: /tns.csi/i });
await expect(entry).toBeVisible();
await entry.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/tns-csi/);
await expect(page.getByRole('heading', { name: /tns.csi.*overview/i })).toBeVisible();
});
test('TNS CSI page renders content', async ({ page }) => {
await page.goto('/c/main/tns-csi');
await waitForSidebar(page);
await expect(page.getByRole('heading', { name: /tns.csi.*overview/i })).toBeVisible({
timeout: 15_000,
});
const hasTable = await page.locator('table').first().isVisible().catch(() => false);
const hasContent = await page.locator('[class*="Mui"]').first().isVisible().catch(() => false);
expect(hasTable || hasContent).toBe(true);
});
test('plugin settings page shows TNS CSI plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
await page.waitForLoadState('networkidle');
const pluginEntry = page.locator('text=/tns.csi/i').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
-18188
View File
File diff suppressed because it is too large Load Diff
+32 -3
View File
@@ -1,6 +1,6 @@
{
"name": "tns-csi",
"version": "0.2.6",
"version": "1.0.0",
"description": "Headlamp plugin for TNS-CSI driver visibility and benchmarking",
"repository": {
"type": "git",
@@ -12,6 +12,7 @@
"homepage": "https://github.com/privilegedescalation/headlamp-tns-csi-plugin#readme",
"author": "privilegedescalation",
"license": "Apache-2.0",
"packageManager": "pnpm@10.32.1",
"scripts": {
"start": "headlamp-plugin start",
"build": "headlamp-plugin build",
@@ -22,9 +23,37 @@
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"e2e": "playwright test",
"e2e:headed": "playwright test --headed"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0"
"@headlamp-k8s/eslint-config": "^0.6.0",
"@kinvolk/headlamp-plugin": "^0.13.0",
"@mui/material": "^5.15.14",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"eslint": "^8.57.0",
"jsdom": "^24.0.0",
"notistack": "^3.0.0",
"prettier": "^2.8.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^5.3.0",
"typescript": "~5.6.2",
"vitest": "^3.2.4",
"@playwright/test": "^1.58.2"
},
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3",
"vite": ">=6.4.2"
}
}
+27
View File
@@ -0,0 +1,27 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30_000,
expect: { timeout: 10_000 },
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: 'list',
use: {
baseURL: process.env.HEADLAMP_URL || (() => { throw new Error('HEADLAMP_URL is required — run scripts/deploy-e2e-headlamp.sh first'); })(),
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/, timeout: 60_000 },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'e2e/.auth/state.json',
},
dependencies: ['setup'],
},
],
});
+12028
View File
File diff suppressed because it is too large Load Diff
+2 -16
View File
@@ -1,19 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"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"
}
]
"extends": ["github>privilegedescalation/.github:renovate-config"]
}
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST_DIR="$REPO_ROOT/dist"
E2E_NAMESPACE="${E2E_NAMESPACE:-headlamp-dev}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
HEADLAMP_VERSION="${HEADLAMP_VERSION:-latest}"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'pnpm build' first." >&2
exit 1
fi
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
exit 1
fi
echo "=== E2E Headlamp Deployment ==="
echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}"
echo " Namespace: $E2E_NAMESPACE"
echo " Release: $E2E_RELEASE"
echo ""
echo "Creating ConfigMap with plugin files..."
kubectl delete configmap headlamp-tns-csi-plugin -n "$E2E_NAMESPACE" --ignore-not-found
kubectl create configmap headlamp-tns-csi-plugin -n "$E2E_NAMESPACE" --from-file="$DIST_DIR" --from-file=package.json="$REPO_ROOT/package.json"
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
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: tns-csi-plugin
mountPath: /headlamp/plugins/headlamp-tns-csi
readOnly: true
volumes:
- name: tns-csi-plugin
configMap:
name: headlamp-tns-csi-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
SVC_URL="http://${E2E_RELEASE}.${E2E_NAMESPACE}.svc.cluster.local"
echo ""
echo "Waiting for ${SVC_URL} to be reachable..."
ATTEMPTS=0
MAX_ATTEMPTS=24
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 ""
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 "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."
fi
echo ""
echo "E2E deployment complete."
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
E2E_NAMESPACE="${E2E_NAMESPACE:-headlamp-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-tns-csi-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
if [ -f "$REPO_ROOT/.env.e2e" ]; then
rm "$REPO_ROOT/.env.e2e"
echo "Removed .env.e2e"
fi
echo ""
echo "E2E teardown complete."
+1 -1
View File
@@ -2,7 +2,7 @@
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
"compilerOptions": {
"module": "esnext",
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "@testing-library/jest-dom"]
"types": ["vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src"]
}
+3
View File
@@ -1,6 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
define: {
'process.env.NODE_ENV': '"test"',
},
test: {
globals: true,
environment: 'jsdom',