Compare commits

..

14 Commits

Author SHA1 Message Date
Chris Farhood b9ca87aa05 fix: update pnpm-lock.yaml for @playwright/test (PRI-726) 2026-05-06 14:08:04 +00:00
Chris Farhood 28bf57060b fix: restore pnpm.overrides from main to fix ERR_PNPM_LOCKFILE_CONFIG_MISMATCH (PRI-726) 2026-05-06 14:03:56 +00:00
Chris Farhood b4600d07d8 Add E2E workflow to headlamp-argocd-plugin
- Adds E2E workflow with deploy/teardown scripts
- Adds Playwright smoke tests for ArgoCD sidebar and app list
- Uses unique release name to avoid collision with shared runner
2026-05-06 13:37:34 +00:00
privilegedescalation-ceo[bot] dedf6538c7 Merge pull request #26 from privilegedescalation/fix/elliptic-vulnerability-override
fix: override elliptic to patched version for GHSA-848j-6mx2-7j84
2026-05-05 18:40:42 +00:00
Chris Farhood 0af4939d8e chore: update pnpm lockfile for elliptic override
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 12:58:43 +00:00
Chris Farhood c24e96da97 fix: override elliptic to patched version for GHSA-848j-6mx2-7j84 2026-05-05 12:51:05 +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
11 changed files with 174 additions and 143 deletions
+132 -5
View File
@@ -14,10 +14,137 @@ concurrency:
group: e2e-${{ github.repository }}
cancel-in-progress: false
env:
E2E_NAMESPACE: headlamp-dev
E2E_RELEASE: headlamp-e2e-argocd
HEADLAMP_VERSION: v0.40.1
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
runs-on: runners-privilegedescalation
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Detect package manager
id: pkg-manager
run: |
if [ -f "pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
else
echo "manager=npm" >> $GITHUB_OUTPUT
echo "has_package_manager=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
- name: Setup pnpm (Corepack, respects packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
run: |
npm install -g corepack
corepack enable pnpm
corepack prepare $(node -p "require('./package.json').packageManager") --activate
- name: Setup pnpm (version latest, no packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
uses: pnpm/action-setup@v5
with:
run_install: false
version: latest
- name: Get pnpm store directory
id: pnpm-store
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.pkg-manager.outputs.manager == 'pnpm'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Install dependencies
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile
else
npm ci
fi
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Deploy E2E Headlamp instance
run: scripts/deploy-e2e-headlamp.sh
- name: Load E2E environment
run: |
if [ -f .env.e2e ]; then
cat .env.e2e >> "$GITHUB_ENV"
else
echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
exit 1
fi
- name: Install Playwright browsers
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm exec playwright install --with-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: Run E2E tests
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run e2e
else
npm run e2e
fi
env:
HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
- name: Collect deployment diagnostics on failure
if: failure()
run: |
echo "=== Pod state ==="
kubectl get pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Pod describe ==="
kubectl describe pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Recent namespace events ==="
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -20 || true
- name: Teardown E2E instance
if: always()
run: scripts/teardown-e2e-headlamp.sh
- name: Upload Playwright report
uses: actions/upload-artifact@v7
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results
path: test-results/
retention-days: 7
+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:
+8 -40
View File
@@ -1,50 +1,18 @@
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();
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
await expect(sidebar.getByRole('button', { name: 'ArgoCD' })).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 }) => {
test('applications list page loads', 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);
await expect(
page.getByRole('heading', { name: /argo.*cd/i })
).toBeVisible({ timeout: 15_000 });
});
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 });
});
});
});
+5 -40
View File
@@ -2,35 +2,6 @@ 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)$/);
@@ -51,19 +22,13 @@ async function authenticateWithToken(page: Page, token: string): Promise<void> {
}
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'
);
if (!token) {
throw new Error('Set HEADLAMP_TOKEN for token auth');
}
await authenticateWithToken(page, token);
await page.context().storageState({ path: AUTH_STATE_PATH });
});
});
+6 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@privilegedescalation/headlamp-argocd-plugin",
"version": "0.1.0",
"description": "Headlamp plugin for ArgoCD visibility monitors ArgoCD Applications, Rollouts, and health status",
"version": "0.1.2",
"description": "Headlamp plugin for ArgoCD visibility \u2014 monitors ArgoCD Applications, Rollouts, and health status",
"repository": {
"type": "git",
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin.git"
@@ -35,13 +35,13 @@
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3",
"flatted": "^3.4.2"
"flatted": "^3.4.2",
"elliptic": ">=6.6.1"
}
},
"devDependencies": {
"@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",
@@ -56,6 +56,7 @@
"react-dom": "^18.3.1",
"react-router-dom": "^5.3.0",
"typescript": "~5.6.2",
"vitest": "^3.0.5"
"vitest": "^3.0.5",
"@playwright/test": "^1.58.2"
}
}
+2 -2
View File
@@ -9,7 +9,7 @@ export default defineConfig({
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'); })(),
baseURL: process.env.HEADLAMP_URL || 'http://headlamp-e2e-argocd.headlamp-dev.svc.cluster.local',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
@@ -24,4 +24,4 @@ export default defineConfig({
dependencies: ['setup'],
},
],
});
});
+1
View File
@@ -8,6 +8,7 @@ overrides:
tar: ^7.5.11
undici: ^7.24.3
flatted: ^3.4.2
elliptic: '>=6.6.1'
importers:
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>privilegedescalation/.github:renovate-config"]
}
+4 -13
View File
@@ -1,24 +1,15 @@
#!/usr/bin/env bash
# deploy-e2e-headlamp.sh
#
# Deploys a stock Headlamp instance with the argocd plugin loaded via
# a ConfigMap volume mount.
#
# Environment:
# E2E_NAMESPACE — namespace (default: headlamp-dev)
# E2E_RELEASE — release 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}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e-argocd}"
HEADLAMP_VERSION="${HEADLAMP_VERSION:-v0.40.1}"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'pnpm build' first." >&2
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
exit 1
fi
@@ -179,4 +170,4 @@ else
fi
echo ""
echo "E2E deployment complete."
echo "E2E deployment complete."
+8 -28
View File
@@ -1,37 +1,17 @@
#!/usr/bin/env bash
# teardown-e2e-headlamp.sh
#
# Tears down the dedicated E2E Headlamp instance.
#
# Environment:
# E2E_NAMESPACE — namespace to clean up (default: headlamp-dev)
# E2E_RELEASE — release 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}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e-argocd}"
echo "=== E2E Headlamp Teardown ==="
echo "=== E2E 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
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete configmap headlamp-argocd-plugin -n "$E2E_NAMESPACE" --ignore-not-found || true
echo "Cleaning up ConfigMap..."
kubectl delete configmap headlamp-argocd-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."
echo "Teardown complete."