Compare commits

..

14 Commits

Author SHA1 Message Date
Chris Farhood fe69dee84e fix e2e: use button role for storage classes sidebar selector
The storage classes sidebar entry is rendered as a button, not a link.
Fix the flaky E2E test by changing getByRole('link') to getByRole('button').

Fixes PRI-935

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 17:11:07 +00:00
Chris Farhood 215c79ae19 fix(e2e): write HEADLAMP_URL before token gen; add pods RBAC
Fix two bugs from PRI-879 QA review:

- HEADLAMP_URL is now written to .env.e2e unconditionally, before
  attempting token generation. Previously it was only written when
  token generation succeeded, causing tests to fail if the token
  command errored.

- ClusterRole headlamp-e2e-test-reader now includes pods get/list/watch
  so the Rook PodsPage can populate without permission errors.

Does not address the popup race in auth.setup.ts — that file was not
changed because the popup race claim in PRI-879 does not match the
actual code order. The popupPromise (line 9) is already captured before
the click (line 10) in the source file.

Fixes: PRI-879

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 13:33:41 +00:00
Chris Farhood 169d2ec91b fix(e2e): add cluster-scoped RBAC for E2E service account
The headlamp-e2e-test service account needs cluster-wide read permissions
for storageclasses, cephclusters, persistentvolumes, and
persistentvolumeclaims so the Rook plugin sidebar can populate these
resources without errors.

- Add ClusterRole headlamp-e2e-test-reader with get/list/watch on
  storageclasses, cephclusters, cephclusters/status, persistentvolumes,
  persistentvolumeclaims
- Add ClusterRoleBinding headlamp-e2e-test-crb binding the role to
  the headlamp-e2e-test service account
- Update teardown to also clean up the ClusterRole and ClusterRoleBinding

Fixes: PRI-741

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 16:12:03 +00:00
privilegedescalation-ceo[bot] edd4404e70 Merge pull request #49 from privilegedescalation/hugh/add-e2e-infra-rook-pri-640
Add E2E test infrastructure for rook plugin
2026-05-05 10:30:53 +00:00
privilegedescalation-ceo[bot] 7f1e27d5c8 Merge pull request #50 from privilegedescalation/gandalf/fix-e2e-pri-657
fix(e2e): add waitForSidebar helper and networkidle waits for reliability
2026-05-05 10:30:48 +00:00
Chris Farhood 8d2ec06e41 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 b6941756f7 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:19 +00:00
Chris Farhood 8a36950235 Add E2E test infrastructure for rook plugin
- playwright.config.ts with authenticated test projects
- e2e/auth.setup.ts authenticates via OIDC or token
- e2e/rook.spec.ts smoke tests for sidebar, overview page,
  storage classes navigation, and plugin settings
- scripts/deploy-e2e-headlamp.sh deploys Headlamp + rook in headlamp-dev
- scripts/teardown-e2e-headlamp.sh cleans up after tests
- e2e.yaml uses reusable workflow from .github repo
- @playwright/test ^1.58.2 devDep added

- PRI-640

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 05:14:41 +00:00
privilegedescalation-engineer[bot] 30a38e7ed0 CI: trigger on dev branch for development workflow (#48)
Co-authored-by: Chris Farhood <chris@farhood.org>
2026-05-04 21:19:26 +00:00
privilegedescalation-engineer[bot] 7ef6e7ee7b chore: update ArtifactHub namespace from privilegedescalation to headlamp (#47)
Co-authored-by: Chris Farhood <chris@farhood.org>
2026-05-04 21:19:12 +00:00
privilegedescalation-engineer[bot] 2e80c3f0ca fix: add markdownlint config to resolve CI failures (#46)
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 20:02:43 +00:00
privilegedescalation-engineer[bot] 0af4096b4f fix: override lodash >=4.18.0 to patch code injection vulnerability (#38)
* fix: override lodash >=4.18.0 to patch code injection vulnerability

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

* 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>

* Remove stray lodash devDependency to fix CI EOVERRIDE

The previous commit added lodash@4.18.1 as a direct devDependency
alongside the overrides.lodash >=4.18.0 entry. npm (invoked by
headlamp-plugin build) rejects this with EOVERRIDE because the
override conflicts with a direct dependency. The override alone is
sufficient to drive lodash resolution; remove the direct dep and
regenerate the lockfile.

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:43 +00:00
privilegedescalation-engineer[bot] d44ae043c3 fix: update vite to >=6.4.2 to patch arbitrary file read vulnerability (#37)
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:08 +00:00
privilegedescalation-engineer[bot] 39ed3ea90a release: v1.0.2 (#36)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 04:00:27 +00:00
14 changed files with 1629 additions and 650 deletions
+2 -2
View File
@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: [main]
branches: [main, dev]
pull_request:
branches: [main]
branches: [main, dev]
workflow_dispatch:
workflow_call:
+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
+6
View File
@@ -6,3 +6,9 @@ dist/
.env.local
.eslintcache
.playwright-mcp/
# 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/**
+4
View File
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- **ArtifactHub namespace** — updated `provider.name` and `maintainers[].name` in `artifacthub-pkg.yml` from `privilegedescalation` to `headlamp` to reflect the ArtifactHub package namespace
## [1.0.0] - 2026-03-24
### Added
+2 -2
View File
@@ -18,10 +18,10 @@ links:
- name: source
url: https://github.com/privilegedescalation/headlamp-rook-plugin
maintainers:
- name: privilegedescalation
- name: headlamp
email: privilegedescalation@users.noreply.github.com
provider:
name: privilegedescalation
name: headlamp
changes:
- kind: changed
description: "Bump to v1.0.1 patch release — fix ArtifactHub checksum"
+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 });
});
+63
View File
@@ -0,0 +1,63 @@
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('Rook plugin smoke tests', () => {
test('sidebar contains Rook entry', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
await expect(sidebar.getByRole('button', { name: /rook/i })).toBeVisible();
});
test('Rook sidebar entry navigates to overview', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
const rookEntry = sidebar.getByRole('button', { name: /rook/i });
await expect(rookEntry).toBeVisible();
await rookEntry.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/rook-ceph/);
await expect(page.getByRole('heading', { name: /overview/i })).toBeVisible();
});
test('overview page renders content', async ({ page }) => {
await page.goto('/c/main/rook-ceph');
await waitForSidebar(page);
await expect(page.getByRole('heading', { name: /overview/i })).toBeVisible({
timeout: 15_000,
});
const hasContent = await page.locator('text=/cluster|ceph|status/i').first().isVisible().catch(() => false);
const hasDashboard = await page.locator('[class*="Mui"]').first().isVisible().catch(() => false);
expect(hasContent || hasDashboard).toBe(true);
});
test('navigation to storage classes view works', async ({ page }) => {
await page.goto('/c/main/rook-ceph');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
const storageClassesLink = sidebar.getByRole('button', { name: /storage classes/i });
await expect(storageClassesLink).toBeVisible({ timeout: 10_000 });
await storageClassesLink.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/rook-ceph\/storage-classes/);
await expect(page.getByRole('heading', { name: /storage class/i })).toBeVisible({ timeout: 15_000 });
});
test('plugin settings page shows rook plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
await page.waitForLoadState('networkidle');
const pluginEntry = page.locator('text=rook').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
+8 -3
View File
@@ -22,12 +22,15 @@
"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"
},
"devDependencies": {
"@headlamp-k8s/eslint-config": "^0.6.0",
"@kinvolk/headlamp-plugin": "^0.13.0",
"@mui/material": "^5.15.14",
"@playwright/test": "^1.58.2",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
@@ -45,6 +48,8 @@
},
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3"
"undici": "^7.24.3",
"vite": ">=6.4.2",
"lodash": ">=4.18.0"
}
}
}
+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'],
},
],
});
+1095 -643
View File
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
#!/usr/bin/env bash
# deploy-e2e-headlamp.sh
#
# Deploys a stock Headlamp instance with the rook plugin loaded via
# a ConfigMap volume mount.
#
# E2E resources are deployed to the `headlamp-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
#
# Environment:
# E2E_NAMESPACE — namespace for E2E Headlamp (default: headlamp-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
# HEADLAMP_VERSION — Headlamp image tag (default: latest)
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-rook-plugin \
-n "$E2E_NAMESPACE" --ignore-not-found
kubectl create configmap headlamp-rook-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 clusterrolebinding headlamp-e2e-test-crb --ignore-not-found 2>/dev/null || true
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
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found 2>/dev/null || true
echo ""
echo "Creating E2E service account..."
kubectl create serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE"
echo ""
echo "Creating RBAC for E2E service account..."
kubectl apply -f - <<EOF
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: headlamp-e2e-test-reader
rules:
- apiGroups: [""]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["ceph.rook.io"]
resources: ["cephclusters"]
verbs: ["get", "list", "watch"]
- apiGroups: ["ceph.rook.io"]
resources: ["cephclusters/status"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: headlamp-e2e-test-crb
subjects:
- kind: ServiceAccount
name: headlamp-e2e-test
namespace: ${E2E_NAMESPACE}
roleRef:
kind: ClusterRole
name: headlamp-e2e-test-reader
apiGroup: rbac.authorization.k8s.io
EOF
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: rook-plugin
mountPath: /headlamp/plugins/headlamp-rook
readOnly: true
volumes:
- name: rook-plugin
configMap:
name: headlamp-rook-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 "Writing E2E env file..."
echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e"
echo ""
echo "Creating service account token for E2E auth..."
TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "")
if [ -n "$TOKEN" ]; then
echo "HEADLAMP_TOKEN=${TOKEN}" >> "$REPO_ROOT/.env.e2e"
echo "Wrote .env.e2e with HEADLAMP_URL and HEADLAMP_TOKEN"
else
echo "Wrote .env.e2e with HEADLAMP_URL only (token generation failed, using OIDC fallback)"
fi
echo ""
echo "E2E deployment complete."
+39
View File
@@ -0,0 +1,39 @@
#!/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: headlamp-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
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-rook-plugin -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up test service account and RBAC..."
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found
kubectl delete clusterrolebinding headlamp-e2e-test-crb --ignore-not-found 2>/dev/null || true
kubectl delete clusterrole headlamp-e2e-test-reader --ignore-not-found 2>/dev/null || true
if [ -f "$REPO_ROOT/.env.e2e" ]; then
rm "$REPO_ROOT/.env.e2e"
echo "Removed .env.e2e"
fi
echo ""
echo "E2E teardown complete."