Compare commits

...

8 Commits

Author SHA1 Message Date
Chris Farhood 83a453a0f0 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 22:24:53 +00:00
Chris Farhood 8f8c485228 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-06 22:24:53 +00:00
Chris Farhood 6c6cfc88f4 fix(e2e): add cluster diagnostics to deploy step for faster triage
Add pre-deployment node/namespace/resource diagnostics and wrap
kubectl apply in explicit error handling with cluster state dump on
failure. This gives us actionable output in the GitHub Actions logs
when the Deploy E2E step fails, instead of a silent exit code.

PRI-956

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 18:30:43 +00:00
Chris Farhood 540f0a7890 fix e2e: add missing plugin-name input to plugin-e2e workflow
The rook plugin E2E workflow was calling the reusable plugin-e2e workflow
without the required plugin-name input. This caused the ConfigMap naming
and mount path to fall back to the headlamp-kube-vip defaults, breaking
E2E runs for the rook plugin.

Fix: pass plugin-name: rook to the reusable workflow.
2026-05-06 12:36:08 +00:00
Chris Farhood 3f93e71f28 fix(e2e): reference @main workflow after .github merge
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 17:43:54 +00:00
Chris Farhood 0d9f9d859a fix(e2e): use .first() to handle strict mode violations (PRI-699)
Similar to the kube-vip fix, /overview/i and /storage class/i can
match multiple headings. Using .first() to avoid strict mode violations.
2026-05-05 13:56:35 +00:00
Chris Farhood 61df61c691 fix(e2e): expand storage classes sidebar nav and relax plugin settings locator
The 'Storage Classes' link is nested under the Rook sidebar button, not
at the top level. Expand the Rook section before asserting visibility.
Also uses /rook/i case-insensitive regex and waits for the plugins list
to render before searching.
2026-05-05 13:04:45 +00:00
Chris Farhood 15d161c312 fix(e2e): use pnpm-capable workflow branch with namespace param 2026-05-05 12:21:22 +00:00
4 changed files with 88 additions and 15 deletions
+3 -2
View File
@@ -16,8 +16,9 @@ concurrency:
jobs:
e2e:
uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e
uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@main
with:
node-version: '22'
node-version: "22"
headlamp-version: v0.40.1
e2e-namespace: headlamp-dev
plugin-name: rook
+10 -5
View File
@@ -24,14 +24,14 @@ test.describe('Rook plugin smoke tests', () => {
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/rook-ceph/);
await expect(page.getByRole('heading', { name: /overview/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /overview/i }).first()).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({
await expect(page.getByRole('heading', { name: /overview/i }).first()).toBeVisible({
timeout: 15_000,
});
@@ -42,22 +42,27 @@ test.describe('Rook plugin smoke tests', () => {
test('navigation to storage classes view works', async ({ page }) => {
await page.goto('/c/main/rook-ceph');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
const rookBtn = sidebar.getByRole('button', { name: /rook/i });
await rookBtn.click();
await page.waitForLoadState('networkidle');
const storageClassesLink = sidebar.getByRole('link', { 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 });
await expect(page.getByRole('heading', { name: /storage class/i }).first()).toBeVisible({ timeout: 15_000 });
});
test('plugin settings page shows rook plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
await page.waitForLoadState('networkidle');
await page.waitForSelector('table, [class*="PluginList"], [class*="plugin"]', { timeout: 10_000 }).catch(() => {});
const pluginEntry = page.locator('text=rook').first();
const pluginEntry = page.locator('text=/rook/i').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
+72 -7
View File
@@ -35,6 +35,17 @@ if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/nul
exit 1
fi
echo ""
echo "=== Pre-deployment cluster diagnostics ==="
echo "Nodes:"
kubectl get nodes -o wide 2>&1 || true
echo ""
echo "headlamp-dev namespace state:"
kubectl get ns headlamp-dev -o yaml 2>&1 || true
echo ""
echo "Existing E2E resources in namespace:"
kubectl get all -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== E2E Headlamp Deployment ==="
echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}"
echo " Namespace: $E2E_NAMESPACE"
@@ -53,14 +64,62 @@ kubectl create configmap headlamp-rook-plugin \
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
if ! kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
@@ -113,7 +172,7 @@ spec:
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 6
failureThreshold: 6
livenessProbe:
httpGet:
path: /
@@ -148,6 +207,12 @@ spec:
targetPort: http
protocol: TCP
EOF
then
echo "ERROR: kubectl apply failed. Dumping cluster state..." >&2
kubectl get all -n "$E2E_NAMESPACE" 2>&1 || true
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -30 || true
exit 1
fi
echo "Waiting for rollout..."
kubectl rollout status "deployment/${E2E_RELEASE}" \
@@ -172,17 +237,17 @@ 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 -
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_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."
echo "Wrote .env.e2e with HEADLAMP_URL only (token generation failed, using OIDC fallback)"
fi
echo ""
+3 -1
View File
@@ -25,8 +25,10 @@ kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-
echo "Cleaning up ConfigMap..."
kubectl delete configmap headlamp-rook-plugin -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up test service account..."
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"