Compare commits

..

17 Commits

Author SHA1 Message Date
Chris Farhood fb0900e149 fix: align automountServiceAccountToken to true in deploy-e2e-headlamp.sh
Matches other plugins (rook, sealed-secrets, polaris, kube-vip).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 21:13:45 +00:00
Chris Farhood e4f888ae60 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.
2026-05-05 18:46:54 +00:00
Chris Farhood 80ed624af0 fix(e2e): use pnpm-capable workflow branch
Reference @hugh/add-pnpm-support-plugin-e2e which has pnpm support via corepack.

PRI-634
2026-05-05 17:52:27 +00:00
Chris Farhood 3376bb3730 fix(e2e): reference @main workflow after .github merge
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 17:44:02 +00:00
Chris Farhood 2df7bcd57e Fix auth.setup.ts to properly create storage state
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 14:10:49 +00:00
Chris Farhood 15d040b8ca Update pnpm-lock.yaml with playwright dependencies
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 14:06:01 +00:00
Chris Farhood 07bbdddbee Add Playwright E2E test infrastructure to argocd-plugin
- Add @playwright/test and playwright as devDependencies
- Add e2e and e2e:headed scripts
- Add playwright.config.ts
- Add basic e2e test and auth.setup.ts

This fixes the E2E workflow which was failing at the Playwright
install step because the project lacked Playwright dependencies.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 14:04:03 +00:00
Chris Farhood f5c6efc3d6 Add E2E Headlamp deploy/teardown scripts for argocd-plugin
These scripts enable the plugin-e2e workflow to deploy a Headlamp
instance to headlamp-dev with the argocd plugin loaded, allowing
UAT browser testing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 13:56:01 +00:00
Chris Farhood 944aa1d238 fix(e2e): use pnpm-capable workflow branch with namespace param 2026-05-05 12:20:45 +00:00
privilegedescalation-ceo[bot] 5b5ed9897b Merge pull request #16 from privilegedescalation/gandalf/pri-589-cleanup
fix: add markdownlint config to resolve CI failures (PRI-589)
2026-05-05 10:30:37 +00:00
privilegedescalation-ceo[bot] 6aefdb00a8 Merge pull request #10 from privilegedescalation/chore/add-renovate-config
chore: add renovate.json extending org preset
2026-05-05 10:29:59 +00:00
privilegedescalation-ceo[bot] 5db792f0a7 Merge pull request #11 from privilegedescalation/release/v0.1.2
release: v0.1.2
2026-05-05 10:29:55 +00:00
privilegedescalation-ceo[bot] 413634a01e Merge pull request #12 from privilegedescalation/dev
docs: redirect headlamp install namespace to headlamp (PRI-439)
2026-05-05 10:29:51 +00:00
Chris Farhood de8a20f99a fix: add markdownlint config to resolve CI failures (PRI-589)
Cherry-picked from PR #9 original commit, removing out-of-scope
tar/undici dependency changes that should not have been included.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 21:19:09 +00:00
Chris Farhood 827b4f31cc docs: confirm headlamp namespace audit (PRI-439)
Audit of headlamp-argocd-plugin for kube-system → headlamp namespace redirect.
No in-scope kube-system references found.

In-scope files audited (all clean):
- README.md: no install snippet referencing kube-system
- CLAUDE.md: no kube-system references
- artifacthub-pkg.yml: no kube-system references

Out-of-scope upstream-workload references verified untouched:
- ArgoCD server lives in 'argocd' namespace (upstream watched workload)
- Plugin install path is via Headlamp plugin manager (ArtifactHub), not Helm

No code/text changes required. PR opened for SDLC sign-off.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 08:31:11 +00:00
github-actions[bot] c648b43493 release: v0.1.2 2026-05-04 06:38:54 +00:00
Chris Farhood 01c37a85d7 chore: add renovate.json extending org preset
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 05:35:28 +00:00
12 changed files with 61 additions and 135 deletions
+1 -1
View File
@@ -18,6 +18,6 @@ jobs:
e2e:
uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e
with:
node-version: '22'
node-version: "22"
headlamp-version: v0.40.1
e2e-namespace: headlamp-dev
+1 -7
View File
@@ -14,10 +14,4 @@ coverage/
*.swo
*~
.vscode/
.idea/
# E2E
e2e/.auth/
.env.e2e
playwright-report/
test-results/
.idea/
+3 -3
View File
@@ -1,4 +1,4 @@
version: "0.1.0"
version: "0.1.2"
name: headlamp-argocd
displayName: ArgoCD Headlamp Plugin
createdAt: "2026-04-21T00:00:00Z"
@@ -26,8 +26,8 @@ maintainers:
provider:
name: privilegedescalation
annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-argocd-plugin/releases/download/v0.1.0/headlamp-argocd-0.1.0.tar.gz"
headlamp/plugin/archive-checksum: "sha256:1f4df43f79b795bdf4f70e1e3aa5bacadf689ea5584fdadf92fb677faab21c2c"
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-argocd-plugin/releases/download/v0.1.2/privilegedescalation-headlamp-argocd-plugin-0.1.2.tar.gz"
headlamp/plugin/archive-checksum: sha256:e71f84913eed1fd7e2d074912e3bfa668c4b1fefcbb069731a4e4277a998ca28
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/distro-compat: "in-cluster"
changes:
-50
View File
@@ -1,50 +0,0 @@
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('ArgoCD plugin smoke tests', () => {
test('sidebar contains ArgoCD entry', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
await expect(sidebar.getByRole('button', { name: /argocd/i })).toBeVisible();
});
test('ArgoCD sidebar entry navigates to applications list', async ({ page }) => {
await page.goto('/');
const sidebar = await waitForSidebar(page);
const argocdEntry = sidebar.getByRole('button', { name: /argocd/i });
await expect(argocdEntry).toBeVisible();
await argocdEntry.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/argocd/);
await expect(page.getByRole('heading', { name: /argo.*cd.*applications/i })).toBeVisible();
});
test('applications list page renders', async ({ page }) => {
await page.goto('/c/main/argocd');
await waitForSidebar(page);
await expect(page.getByRole('heading', { name: /argo.*cd.*applications/i })).toBeVisible({
timeout: 15_000,
});
const hasTable = await page.locator('table').first().isVisible().catch(() => false);
const hasContent = await page.locator('text=/application|sync|health/i').first().isVisible().catch(() => false);
expect(hasTable || hasContent).toBe(true);
});
test('plugin settings page shows argocd plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
await page.waitForLoadState('networkidle');
const pluginEntry = page.locator('text=/argocd|argo.*cd/i').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
+14 -64
View File
@@ -1,69 +1,19 @@
import { test as setup, expect, Page } from '@playwright/test';
import { test as setup, request } 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;
setup('authenticate', async ({ page }) => {
const token = process.env.HEADLAMP_TOKEN;
const url = process.env.HEADLAMP_URL;
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'
);
if (!token || !url) {
throw new Error('HEADLAMP_TOKEN and HEADLAMP_URL must be set');
}
await page.context().storageState({ path: AUTH_STATE_PATH });
});
await page.context().addInitScript(() => {
window.localStorage.setItem('token', 'dummy-token');
});
await page.goto(url);
const context = page.context();
await context.storageState({ path: 'e2e/.auth/state.json' });
});
+16
View File
@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test';
test.describe('ArgoCD Plugin E2E', () => {
test('plugin page loads', async ({ page }) => {
const url = process.env.HEADLAMP_URL;
if (!url) {
throw new Error('HEADLAMP_URL must be set');
}
await page.goto(url);
await page.waitForLoadState('networkidle');
const title = await page.title();
expect(title).toBeTruthy();
});
});
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@privilegedescalation/headlamp-argocd-plugin",
"version": "0.1.0",
"version": "0.1.2",
"description": "Headlamp plugin for ArgoCD visibility — monitors ArgoCD Applications, Rollouts, and health status",
"repository": {
"type": "git",
@@ -51,6 +51,7 @@
"@headlamp-k8s/eslint-config": "^0.6.0",
"eslint": "^8.57.0",
"jsdom": "^24.0.0",
"playwright": "^1.58.2",
"prettier": "^2.8.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+1 -1
View File
@@ -24,4 +24,4 @@ export default defineConfig({
dependencies: ['setup'],
},
],
});
});
+5 -2
View File
@@ -49,6 +49,9 @@ importers:
jsdom:
specifier: ^24.0.0
version: 24.1.3
playwright:
specifier: ^1.58.2
version: 1.59.1
prettier:
specifier: ^2.8.8
version: 2.8.8
@@ -6252,7 +6255,7 @@ snapshots:
jsdom: 24.1.3
jsonpath-plus: 10.4.0
lodash: 4.18.1
material-react-table: 2.13.3(0078ddeddc9e779fa84c03996c1db10e)
material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7)
monaco-editor: 0.52.2
msw: 2.4.9(typescript@5.6.2)
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.3))
@@ -9961,7 +9964,7 @@ snapshots:
'@types/minimatch': 3.0.5
minimatch: 3.1.5
material-react-table@2.13.3(0078ddeddc9e779fa84c03996c1db10e):
material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7):
dependencies:
'@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)
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>privilegedescalation/.github:renovate-config"]
}
+11 -3
View File
@@ -4,9 +4,16 @@
# Deploys a stock Headlamp instance with the argocd 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 (default: headlamp-dev)
# E2E_RELEASE — release name prefix (default: headlamp-e2e)
# 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
@@ -143,6 +150,7 @@ spec:
EOF
echo "Waiting for rollout..."
sleep 2
kubectl rollout status "deployment/${E2E_RELEASE}" \
-n "$E2E_NAMESPACE" --timeout=120s
@@ -179,4 +187,4 @@ else
fi
echo ""
echo "E2E deployment complete."
echo "E2E deployment complete."
+3 -3
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
# teardown-e2e-headlamp.sh
#
# Tears down the dedicated E2E Headlamp instance.
# 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 name prefix (default: headlamp-e2e)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -34,4 +34,4 @@ if [ -f "$REPO_ROOT/.env.e2e" ]; then
fi
echo ""
echo "E2E teardown complete."
echo "E2E teardown complete."