Compare commits

..

7 Commits

Author SHA1 Message Date
Chris Farhood 584c1226c8 fix(e2e): remove invalid inputs from reusable workflow call
The plugin-e2e.yaml reusable workflow only accepts node-version and
headlamp-version inputs. Remove e2e-namespace and plugin-name which
are not defined and causing startup_failure.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 16:55:33 +00:00
Chris Farhood c5b8eb5c92 fix(e2e): add missing plugin-name input in e2e.yaml (PRI-910)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 16:21:35 +00:00
Chris Farhood 1d506a0149 fix(e2e): use .first() to handle strict mode violations (PRI-701)
Strict mode violations:
- /sealed.secrets/i matches both 'Sealed Secrets' and 'All Sealed Secrets' buttons
- /sealing.key/i may match multiple headings

Using .first() for heading assertions to avoid strict mode.
2026-05-06 16:11:55 +00:00
Chris Farhood 8798cd1709 fix(e2e): add waitForSidebar and networkidle waits + fix nav test (PRI-701)
- Adds waitForSidebar helper with sidebar visibility wait + networkidle
- Fixes 'navigation between sealed-secrets views' to use waitForSidebar and expand sidebar before nav
- Plugin settings test waits for list before searching
2026-05-06 16:11:55 +00:00
privilegedescalation-engineer[bot] 84c947ed69 fix: add elliptic override for GHSA-848j-6mx2-7j84 (#70)
Add pnpm.overrides.elliptic to prevent version regression on
the transitive elliptic vulnerability (CVE-2025-14505).

Vulnerability path:
@kinvolk/headlamp-plugin → vite-plugin-node-polyfills →
node-stdlib-browser → crypto-browserify → browserify-sign → elliptic

Note: pnpm audit will still report the vulnerability until
upstream publishes elliptic 6.6.2+. This override safeguards
against pulling a worse version.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 00:44:24 +00:00
privilegedescalation-engineer[bot] e212e601a9 Reference shared infra RBAC (PRI-750) (#68)
* Regenerate lockfile for lodash+vite overrides

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

* fix: add markdownlint config to resolve CI failures

- Add .markdownlint-cli2.jsonc with 18 rule disables appropriate for plugin docs
- Add .markdownlintignore to skip generated API reference docs
- Fix remaining errors with --fix

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

* Reference shared infra RBAC in deployment scripts

PRI-750: update plugin repos to reference shared infra RBAC (PRI-695 follow-up)

- scripts/deploy-e2e-headlamp.sh: updated RBAC preflight comment and error
  message to reference privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml
- scripts/teardown-e2e-headlamp.sh: added RBAC reference comment

Infra RBAC is the source of truth managed by Flux GitOps. No E2E workflow
exists yet for this plugin.

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 00:44:14 +00:00
privilegedescalation-engineer[bot] e6920dcba4 fix(e2e): add E2E workflow for sealed-secrets plugin (#67)
* fix(e2e): add E2E workflow for headlamp-sealed-secrets-plugin

Adds .github/workflows/e2e.yaml calling the shared plugin-e2e.yaml reusable workflow.
Fixes PRI-729: E2E DNS failure caused by missing E2E workflow in this repo.

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

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

Update workflow_call ref from hugh/add-pnpm-support-plugin-e2e to main
now that .github#144 has merged.

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

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

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

PRI-634

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

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

* fix(e2e): disable automount SA token to avoid kubelet fetch race

Kubelet tries to fetch SA token immediately after deployment creates the pod,
but the SA may not be propagated yet. Setting automountServiceAccountToken: false
avoids this race. The SA token is not needed since E2E tests authenticate
via HEADLAMP_TOKEN passed as env var.

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 00:44:06 +00:00
6 changed files with 18936 additions and 19 deletions
+22
View File
@@ -0,0 +1,22 @@
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@main
with:
node-version: "22"
headlamp-version: v0.40.1
+26 -15
View File
@@ -1,34 +1,40 @@
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('Sealed Secrets plugin smoke tests', () => {
test('sidebar contains sealed-secrets entry', async ({ page }) => {
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
const sidebar = await waitForSidebar(page);
await expect(sidebar.getByRole('button', { name: /sealed.secrets/i })).toBeVisible();
});
test('sidebar sealed-secrets entry is clickable and navigates to list view', async ({ page }) => {
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
const sidebar = await waitForSidebar(page);
const sealedSecretsEntry = sidebar.getByRole('button', { name: /sealed.secrets/i });
await expect(sealedSecretsEntry).toBeVisible();
await sealedSecretsEntry.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/sealedsecrets/);
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible();
});
test('sealed secrets list page renders table or empty state', async ({ page }) => {
await page.goto('/c/main/sealedsecrets');
await waitForSidebar(page);
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({
timeout: 15_000,
});
// Either a populated table or an empty-state indicator must be visible
const hasTable = await page.locator('table').first().isVisible().catch(() => false);
const hasEmptyState = await page
.locator('text=/no.*sealed|no.*secret|0 item|empty/i')
@@ -40,6 +46,7 @@ test.describe('Sealed Secrets plugin smoke tests', () => {
test('sealing keys page renders table or empty state', async ({ page }) => {
await page.goto('/c/main/sealedsecrets/keys');
await waitForSidebar(page);
await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible({
timeout: 15_000,
@@ -56,33 +63,37 @@ test.describe('Sealed Secrets plugin smoke tests', () => {
test('navigation between sealed-secrets views works', async ({ page }) => {
await page.goto('/c/main/sealedsecrets');
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({
timeout: 15_000,
});
const sidebar = await waitForSidebar(page);
const sealedBtn = sidebar.getByRole('button', { name: /sealed.secrets/i }).first();
await sealedBtn.click();
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { name: /sealed.secrets/i }).first()).toBeVisible({ timeout: 15_000 });
// Navigate to Sealing Keys via sidebar
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
const keysLink = sidebar.getByRole('link', { name: /sealing.key/i });
await expect(keysLink).toBeVisible();
await keysLink.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/sealedsecrets\/keys$/);
await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /sealing.key/i }).first()).toBeVisible();
// Navigate back to All Sealed Secrets
const allSecretsLink = sidebar.getByRole('link', { name: /all sealed secrets/i });
await expect(allSecretsLink).toBeVisible();
await allSecretsLink.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/sealedsecrets(?!\/keys)/);
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible();
await expect(page.getByRole('heading', { name: /sealed.secrets/i }).first()).toBeVisible();
});
test('plugin settings page shows sealed-secrets plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
await page.waitForLoadState('networkidle');
await page.waitForSelector('table', { timeout: 10_000 }).catch(() => {});
// Wait for plugin list to load — plugin scripts load asynchronously
const pluginEntry = page.locator('text=sealed-secrets').first();
const pluginEntry = page.locator('text=/sealed.secrets/i').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
+18877
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -53,7 +53,8 @@
"tar": "^7.5.11",
"undici": "^7.24.3",
"vite": ">=6.4.2",
"lodash": ">=4.18.0"
"lodash": ">=4.18.0",
"elliptic": ">=6.6.1"
},
"dependencies": {
"node-forge": "^1.4.0"
+6 -3
View File
@@ -11,7 +11,9 @@
# 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
# RBAC is managed via Flux from privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml.
# The infra repo is the source of truth — do not apply this file directly.
# Apply RBAC first: kubectl apply -f privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml
#
# Environment:
# E2E_NAMESPACE — namespace for E2E Headlamp (default: privilegedescalation-dev)
@@ -35,7 +37,7 @@ 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
echo " Apply RBAC first: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml" >&2
echo " Apply RBAC first: kubectl apply -f privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml" >&2
exit 1
fi
@@ -97,7 +99,7 @@ spec:
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
serviceAccountName: ${E2E_RELEASE}
automountServiceAccountToken: true
automountServiceAccountToken: false
securityContext: {}
containers:
- name: headlamp
@@ -159,6 +161,7 @@ spec:
EOF
echo "Waiting for rollout..."
sleep 2
kubectl rollout status "deployment/${E2E_RELEASE}" \
-n "$E2E_NAMESPACE" --timeout=120s
+3
View File
@@ -3,6 +3,9 @@
#
# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh.
#
# RBAC is managed via Flux from privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml.
# The infra repo is the source of truth — do not apply this file directly.
#
# Environment:
# E2E_NAMESPACE — namespace to clean up (default: privilegedescalation-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)