From fc4640c41815423a17e858f31f95eb8eb5a9985b Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 13:18:51 +0000 Subject: [PATCH 1/6] chore(ci): add audit-ci allowlist for inherited @kinvolk/headlamp-plugin CVEs (PRI-855) CTO decision (PRI-854): high-severity vulns are dev/build-time only and acceptable risk with explicit allowlist. Co-Authored-By: Paperclip --- audit-ci.jsonc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 audit-ci.jsonc diff --git a/audit-ci.jsonc b/audit-ci.jsonc new file mode 100644 index 0000000..c5cd425 --- /dev/null +++ b/audit-ci.jsonc @@ -0,0 +1,20 @@ +{ + // Allowlist for inherited dev-dependency CVEs from @kinvolk/headlamp-plugin + // CTO decision (PRI-854): these high-severity vulns are dev/build-time only, + // trace to @kinvolk/headlamp-plugin transitive deps (Picomatch, Vite, lodash), + // and do NOT ship in production plugin artifacts. + "allowlist": [ + { + "id": "GHSA-hhpm-516h-p3p6", + "reason": "Picomatch ReDoS: devDependency only, does not ship in production plugin bundle" + }, + { + "id": "GHSA-36xf-7xpp-53w5", + "reason": "Vite arbitrary file read: devDependency only, does not ship in production plugin bundle" + }, + { + "id": "GHSA-jf8v-p3pp-93qh", + "reason": "lodash code injection via _.template: devDependency only, does not ship in production plugin bundle" + } + ] +} -- 2.52.0 From e2ae92648c84d54d2cd42faac603a7e20644ae30 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-ceo[bot]" <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 21:34:49 +0000 Subject: [PATCH 2/6] docs: replace hardcoded namespace with placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: update Headlamp install namespace references from kube-system to headlamp Updates all documentation references to the Headlamp install namespace from kube-system to headlamp as part of PRI-433. In-scope files updated: - README.md, SECURITY.md - docs/getting-started/installation.md, quick-start.md, prerequisites.md - docs/deployment/helm.md, kubernetes.md, production.md - docs/troubleshooting/README.md, common-issues.md, rbac-issues.md - docs/user-guide/configuration.md, rbac-permissions.md - docs/TESTING.md, TROUBLESHOOTING.md, DEPLOYMENT.md Out-of-scope (unchanged): - Source files referencing upstream workload namespace - RBAC manifests describing Polaris namespace (polaris ns is unchanged) - NetworkPolicy namespaceSelector (API server runs in kube-system) - design-decisions.md and ARCHITECTURE.md (URL hashes refer to cluster namespaces, not Headlamp install ns) Co-Authored-By: Paperclip * fix: correct RBAC manifest per QA review (PRI-555) - Remove rbac.authorization.k8s.io privilege escalation block - Fix orphaned comment from round 1 - Add EOF newline - Keep serviceaccounts/token for E2E auth (confirmed needed) - Namespace already correct (privilegedescalation-dev) Co-Authored-By: Paperclip * docs: replace hardcoded namespace with placeholder Users choose their own namespace for Headlamp. Replace all hardcoded namespace references (headlamp, kube-system) in user-facing docs with so users substitute their own value. Conventions: - Helm install: --namespace --create-namespace - kubectl commands: -n - YAML metadata: namespace: - Prose: "the namespace where Headlamp is installed" Out-of-scope references left untouched: - kube-system in NetworkPolicy selectors (API server namespace) - polaris namespace references (upstream workload namespace) - Source code and test files Refs: PRI-433 Co-Authored-By: Paperclip * docs: fix remaining hardcoded headlamp namespace to placeholder Prior commit was inconsistent — some files used while DEPLOYMENT.md, TROUBLESHOOTING.md and several troubleshooting/user-guide docs still hardcoded headlamp as the namespace. Co-Authored-By: Paperclip --------- Co-authored-by: Chris Farhood Co-authored-by: Paperclip --- README.md | 4 +-- SECURITY.md | 6 ++-- docs/DEPLOYMENT.md | 4 +-- docs/TESTING.md | 5 ++-- docs/TROUBLESHOOTING.md | 32 ++++++++++---------- docs/deployment/helm.md | 40 ++++++++++++------------- docs/deployment/kubernetes.md | 42 +++++++++++++-------------- docs/deployment/production.md | 30 +++++++++---------- docs/development/testing.md | 5 ++-- docs/getting-started/installation.md | 26 ++++++++--------- docs/getting-started/prerequisites.md | 10 +++---- docs/getting-started/quick-start.md | 10 +++---- docs/troubleshooting/README.md | 10 +++---- docs/troubleshooting/common-issues.md | 32 ++++++++++---------- docs/troubleshooting/rbac-issues.md | 4 +-- docs/user-guide/configuration.md | 2 +- docs/user-guide/rbac-permissions.md | 16 +++++----- 17 files changed, 138 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index d73104f..520f960 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp # adjust to match your Headlamp service account - namespace: kube-system # adjust to match the namespace Headlamp runs in + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -197,7 +197,7 @@ npm test npm run test:watch # E2E tests (Playwright) -export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h) +export HEADLAMP_TOKEN=$(kubectl create token headlamp -n --duration=24h) npm run e2e npm run e2e:headed # see browser ``` diff --git a/SECURITY.md b/SECURITY.md index aa6ca22..3e4217c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -71,7 +71,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -149,7 +149,7 @@ spec: ### Service Account (Default) -Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings. +Headlamp runs with a dedicated service account (`headlamp` in the namespace where Headlamp is installed). All users share the same permissions defined by this service account's RBAC bindings. **Security Considerations:** - All users have identical access to the plugin @@ -317,7 +317,7 @@ All service proxy requests are logged in Kubernetes API audit logs (if enabled): "verb": "get", "requestURI": "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json", "user": { - "username": "system:serviceaccount:kube-system:headlamp", + "username": "system:serviceaccount::headlamp", "groups": ["system:serviceaccounts", "system:authenticated"] } } diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 929fbd5..99a2570 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -33,7 +33,7 @@ kubectl -n polaris get svc polaris-dashboard kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion # Verify Headlamp is deployed -kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp +kubectl -n get pods -l app.kubernetes.io/name=headlamp ``` ## Installation Methods @@ -59,7 +59,7 @@ kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp ```bash helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml ``` diff --git a/docs/TESTING.md b/docs/TESTING.md index dfdfcef..d20ded7 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -268,10 +268,9 @@ npm run e2e ```bash # Create token -export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h) +export HEADLAMP_TOKEN=$(kubectl create token headlamp -n --duration=24h) -# Port-forward for local testing -kubectl port-forward -n kube-system svc/headlamp 4466:80 +kubectl port-forward -n svc/headlamp 4466:80 # Run tests HEADLAMP_URL=http://localhost:4466 npm run e2e diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 5201500..e9499cc 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -33,7 +33,7 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug ```bash # View Headlamp pod logs (plugin sidecar) -kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin +kubectl logs -n deployment/headlamp -c headlamp-plugin # Expected output: # Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz @@ -43,7 +43,7 @@ kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin **Verify plugin files exist**: ```bash -kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/ +kubectl exec -n deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/ # Should show: headlamp-polaris-plugin/ ``` @@ -118,7 +118,7 @@ Expected subjects: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: ``` For OIDC mode: @@ -154,7 +154,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -169,7 +169,7 @@ Service account mode: ```bash # Impersonate Headlamp service account kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ --resource-name=polaris-dashboard \ -n polaris # Expected: yes @@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \ After applying RBAC changes: ```bash -kubectl rollout restart deployment headlamp -n kube-system +kubectl rollout restart deployment headlamp -n ``` --- @@ -490,7 +490,7 @@ Run this script to test all RBAC components: #!/bin/bash NS="polaris" SA="headlamp" -SA_NS="kube-system" +SA_NS="" echo "=== Testing RBAC for Polaris Plugin ===" @@ -529,8 +529,8 @@ echo "=== Test complete ===" Test connectivity from Headlamp to Polaris: ```bash -# Create debug pod in kube-system namespace -kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash +# Create debug pod in headlamp namespace +kubectl run netdebug -n --rm -it --image=nicolaka/netshoot -- bash # Inside pod, test DNS and HTTP nslookup polaris-dashboard.polaris.svc.cluster.local @@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests: ```bash # View recent audit logs (location varies by cluster) -kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard +kubectl logs -n kube-apiserver-* | grep polaris-dashboard # Look for lines with: # "reason": "Forbidden" -# "user": "system:serviceaccount:kube-system:headlamp" +# "user": "system:serviceaccount::headlamp" ``` --- @@ -567,7 +567,7 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard **Check sidecar logs**: ```bash -kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin +kubectl logs -n deployment/headlamp -c headlamp-plugin ``` **Common errors**: @@ -591,7 +591,7 @@ Error: 404 Not Found **Solution**: Verify `archive-url` in plugin config matches GitHub release: ```bash -kubectl get configmap headlamp-plugin-config -n kube-system -o yaml +kubectl get configmap headlamp-plugin-config -n -o yaml ``` Expected format: @@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue: 1. **Version Information**: ```bash - kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image: + kubectl get pods -n -l app.kubernetes.io/name=headlamp -o yaml | grep image: ``` 2. **Plugin Version**: - Check Settings → Plugins in Headlamp UI - - Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json` + - Or: `kubectl exec -n deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json` 3. **Browser Console Output**: @@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue: 5. **Pod Logs**: ```bash - kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100 + kubectl logs -n deployment/headlamp -c headlamp --tail=100 kubectl logs -n polaris deployment/polaris-dashboard --tail=100 ``` diff --git a/docs/deployment/helm.md b/docs/deployment/helm.md index 26c6f1f..00fabc3 100644 --- a/docs/deployment/helm.md +++ b/docs/deployment/helm.md @@ -41,11 +41,11 @@ pluginsManager: ```bash # Install Headlamp helm install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml # Wait for deployment -kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s +kubectl -n wait --for=condition=available deployment/headlamp --timeout=300s ``` After installation, install the plugin via Headlamp UI (**Settings → Plugins → Catalog**). @@ -131,7 +131,7 @@ Deploy: ```bash helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml \ --wait \ --timeout 5m @@ -177,7 +177,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: headlamp-plugin-config - namespace: kube-system + namespace: data: plugin.yml: | - name: headlamp-polaris-plugin @@ -191,7 +191,7 @@ Apply ConfigMap then deploy Headlamp: kubectl apply -f headlamp-plugin-config.yaml helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml ``` @@ -221,7 +221,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: headlamp - namespace: kube-system + namespace: spec: interval: 30m chart: @@ -300,7 +300,7 @@ kubectl apply -f helmrepository.yaml kubectl apply -f helmrelease.yaml # Watch deployment -flux get helmreleases -n kube-system --watch +flux get helmreleases -n --watch ``` ## RBAC Configuration @@ -329,7 +329,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system +namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -349,7 +349,7 @@ helm repo update # Upgrade Headlamp (preserves plugin configuration) helm upgrade headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml \ --wait ``` @@ -365,15 +365,15 @@ helm upgrade headlamp headlamp/headlamp \ ```bash # Update ConfigMap with new version -kubectl -n kube-system edit configmap headlamp-plugin-config +kubectl -n edit configmap headlamp-plugin-config # Update version and URL: # version: 0.3.6 # url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz # Restart deployment to trigger init container -kubectl -n kube-system rollout restart deployment/headlamp -kubectl -n kube-system rollout status deployment/headlamp +kubectl -n rollout restart deployment/headlamp +kubectl -n rollout status deployment/headlamp ``` ## Troubleshooting @@ -382,25 +382,25 @@ kubectl -n kube-system rollout status deployment/headlamp ```bash # Check Headlamp values -helm get values headlamp -n kube-system +helm get values headlamp -n # Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ +kubectl -n exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # If missing, reinstall plugin via UI or check init container logs -kubectl -n kube-system logs deployment/headlamp -c install-polaris-plugin +kubectl -n logs deployment/headlamp -c install-polaris-plugin ``` ### Helm Release Stuck ```bash # Check Helm release status -helm list -n kube-system +helm list -n # If stuck, force upgrade helm upgrade headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml \ --force \ --wait @@ -410,13 +410,13 @@ helm upgrade headlamp headlamp/headlamp \ ```bash # Check HelmRelease status -flux get helmreleases -n kube-system +flux get helmreleases -n # Check events -kubectl -n kube-system describe helmrelease headlamp +kubectl -n describe helmrelease headlamp # Force reconciliation -flux reconcile helmrelease headlamp -n kube-system +flux reconcile helmrelease headlamp -n ``` ## Next Steps diff --git a/docs/deployment/kubernetes.md b/docs/deployment/kubernetes.md index de478c1..211d0cb 100644 --- a/docs/deployment/kubernetes.md +++ b/docs/deployment/kubernetes.md @@ -47,7 +47,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -71,7 +71,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy # Test permission kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard @@ -90,7 +90,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: headlamp-plugin-config - namespace: kube-system + namespace: labels: app.kubernetes.io/name: headlamp app.kubernetes.io/component: plugin-config @@ -109,7 +109,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: headlamp - namespace: kube-system + namespace: labels: app.kubernetes.io/name: headlamp spec: @@ -194,7 +194,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: headlamp - namespace: kube-system + namespace: labels: app.kubernetes.io/name: headlamp @@ -204,7 +204,7 @@ apiVersion: v1 kind: Service metadata: name: headlamp - namespace: kube-system + namespace: labels: app.kubernetes.io/name: headlamp spec: @@ -235,27 +235,27 @@ kubectl apply -f headlamp-service.yaml kubectl apply -f headlamp-serviceaccount.yaml # Wait for deployment to be ready -kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s +kubectl -n wait --for=condition=available deployment/headlamp --timeout=300s ``` ### 2. Verify Deployment ```bash # Check pods are running -kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp +kubectl -n get pods -l app.kubernetes.io/name=headlamp # Expected output: # NAME READY STATUS RESTARTS AGE # headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 2m # Check init container logs -kubectl -n kube-system logs deployment/headlamp -c install-plugins +kubectl -n logs deployment/headlamp -c install-plugins # Expected output: # Plugin installation complete # Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ +kubectl -n exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: @@ -273,7 +273,7 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy ```bash # Port-forward to access locally -kubectl -n kube-system port-forward service/headlamp 8080:80 +kubectl -n port-forward service/headlamp 8080:80 # Open browser to http://localhost:8080 ``` @@ -309,7 +309,7 @@ k8s/ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -namespace: kube-system +namespace: commonLabels: app.kubernetes.io/name: headlamp @@ -401,7 +401,7 @@ spec: - apiVersion: apps/v1 kind: Deployment name: headlamp - namespace: kube-system + namespace: ``` ## Upgrading the Plugin @@ -410,24 +410,24 @@ spec: ```bash # Edit ConfigMap with new version -kubectl -n kube-system edit configmap headlamp-plugin-config +kubectl -n edit configmap headlamp-plugin-config # Update version and URL: # version: 0.3.6 # url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz # Restart deployment to trigger init container -kubectl -n kube-system rollout restart deployment/headlamp +kubectl -n rollout restart deployment/headlamp # Wait for rollout to complete -kubectl -n kube-system rollout status deployment/headlamp +kubectl -n rollout status deployment/headlamp ``` ### Verify Upgrade ```bash # Check init container logs -kubectl -n kube-system logs deployment/headlamp -c install-plugins +kubectl -n logs deployment/headlamp -c install-plugins # Verify new version in UI # Navigate to Settings → Plugins in Headlamp @@ -439,7 +439,7 @@ kubectl -n kube-system logs deployment/headlamp -c install-plugins ```bash # Check init container logs -kubectl -n kube-system logs deployment/headlamp -c install-plugins +kubectl -n logs deployment/headlamp -c install-plugins # Common issues: # 1. Network connectivity to GitHub @@ -451,14 +451,14 @@ kubectl -n kube-system logs deployment/headlamp -c install-plugins ```bash # Verify HEADLAMP_CONFIG_WATCH_PLUGINS is false -kubectl -n kube-system get deployment headlamp -o yaml | grep WATCH_PLUGINS +kubectl -n get deployment headlamp -o yaml | grep WATCH_PLUGINS # Expected output: # - name: HEADLAMP_CONFIG_WATCH_PLUGINS # value: "false" # If not set or "true", update deployment -kubectl -n kube-system edit deployment headlamp +kubectl -n edit deployment headlamp ``` ### RBAC Permissions Denied @@ -466,7 +466,7 @@ kubectl -n kube-system edit deployment headlamp ```bash # Test RBAC kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard diff --git a/docs/deployment/production.md b/docs/deployment/production.md index 18f300f..37fedb7 100644 --- a/docs/deployment/production.md +++ b/docs/deployment/production.md @@ -37,8 +37,8 @@ kubectl -n polaris get svc polaris-dashboard kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion # Verify Headlamp -kubectl -n kube-system get deployment headlamp -kubectl -n kube-system get svc headlamp +kubectl -n get deployment headlamp +kubectl -n get svc headlamp ``` ## Production Checklist @@ -60,17 +60,17 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy # 2. Verify RBAC permissions kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard # Expected: yes # 3. Check Headlamp logs for plugin loading -kubectl -n kube-system logs deployment/headlamp | grep -i polaris +kubectl -n logs deployment/headlamp | grep -i polaris # Expected: No errors related to plugin loading # 4. Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected: dist/, package.json present ``` @@ -241,7 +241,7 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: headlamp-pdb - namespace: kube-system + namespace: spec: minAvailable: 1 selector: @@ -295,7 +295,7 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: headlamp - namespace: kube-system + namespace: spec: selector: matchLabels: @@ -312,10 +312,10 @@ spec: ```bash # View logs -kubectl -n kube-system logs deployment/headlamp -f +kubectl -n logs deployment/headlamp -f # Filter for plugin-related logs -kubectl -n kube-system logs deployment/headlamp | grep -i polaris +kubectl -n logs deployment/headlamp | grep -i polaris ``` **Polaris Dashboard Logs:** @@ -341,14 +341,14 @@ apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: headlamp-alerts - namespace: kube-system + namespace: spec: groups: - name: headlamp interval: 30s rules: - alert: HeadlampPodNotReady - expr: kube_pod_status_ready{namespace="kube-system", pod=~"headlamp-.*"} == 0 + expr: kube_pod_status_ready{namespace="", pod=~"headlamp-.*"} == 0 for: 5m labels: severity: warning @@ -422,9 +422,9 @@ If Headlamp or plugin becomes unavailable: 2. **Redeploy Headlamp:** ```bash - helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ - --values headlamp-values.yaml +helm upgrade --install headlamp headlamp/headlamp \ + --namespace \ + --values headlamp-values.yaml ``` 3. **Reapply RBAC:** @@ -436,7 +436,7 @@ If Headlamp or plugin becomes unavailable: 4. **Verify plugin files:** ```bash - kubectl -n kube-system exec deployment/headlamp -- \ + kubectl -n exec deployment/headlamp -- \ ls /headlamp/plugins/headlamp-polaris-plugin/ ``` diff --git a/docs/development/testing.md b/docs/development/testing.md index dfdfcef..d20ded7 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -268,10 +268,9 @@ npm run e2e ```bash # Create token -export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h) +export HEADLAMP_TOKEN=$(kubectl create token headlamp -n --duration=24h) -# Port-forward for local testing -kubectl port-forward -n kube-system svc/headlamp 4466:80 +kubectl port-forward -n svc/headlamp 4466:80 # Run tests HEADLAMP_URL=http://localhost:4466 npm run e2e diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 3db5527..78c0636 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -72,7 +72,7 @@ Deploy or update Headlamp: ```bash helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml ``` @@ -122,7 +122,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: headlamp-plugin-config - namespace: kube-system + namespace: data: plugin.yml: | - name: headlamp-polaris-plugin @@ -138,14 +138,14 @@ kubectl apply -f headlamp-plugin-config.yaml # Deploy/update Headlamp with sidecar helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml # Wait for pod to be ready -kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s +kubectl -n wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s # Verify plugin files -kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: # drwxr-xr-x dist/ @@ -270,7 +270,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -284,10 +284,10 @@ See [RBAC Permissions](../user-guide/rbac-permissions.md) for detailed RBAC conf ```bash # If you updated Helm values or ConfigMaps -kubectl -n kube-system rollout restart deployment/headlamp +kubectl -n rollout restart deployment/headlamp # Wait for pod to be ready -kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s +kubectl -n wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s ``` ### 3. Clear Browser Cache @@ -312,14 +312,14 @@ kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name= ```bash # Verify plugin files exist -kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ +kubectl -n exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: # drwxr-xr-x dist/ # -rw-r--r-- package.json # Check Headlamp logs for errors -kubectl -n kube-system logs deployment/headlamp | grep -i polaris +kubectl -n logs deployment/headlamp | grep -i polaris # Expected: No errors related to plugin loading @@ -345,13 +345,13 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy ```bash # 1. Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ +kubectl -n exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected: dist/, package.json present # 2. Check Headlamp logs for plugin errors -kubectl -n kube-system logs deployment/headlamp | grep -i polaris +kubectl -n logs deployment/headlamp | grep -i polaris # 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R) @@ -404,7 +404,7 @@ helm install polaris fairwinds-stable/polaris \ ```bash # Wait 30 minutes for ArtifactHub sync # Or manually force Headlamp restart: -kubectl -n kube-system rollout restart deployment/headlamp +kubectl -n rollout restart deployment/headlamp ``` ## Next Steps diff --git a/docs/getting-started/prerequisites.md b/docs/getting-started/prerequisites.md index 2838ee2..9299156 100644 --- a/docs/getting-started/prerequisites.md +++ b/docs/getting-started/prerequisites.md @@ -67,14 +67,14 @@ kubectl -n polaris wait --for=condition=ready pod -l app.kubernetes.io/name=pola ```bash # Check Headlamp is deployed -kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp +kubectl -n get pods -l app.kubernetes.io/name=headlamp # Expected output: # NAME READY STATUS RESTARTS AGE # headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 1h # Check Headlamp version (must be v0.26+) -kubectl -n kube-system get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}' +kubectl -n get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}' # Expected output: # ghcr.io/headlamp-k8s/headlamp:v0.39.0 (or similar) @@ -89,12 +89,12 @@ helm repo update # Install Headlamp helm install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --set config.pluginsDir="/headlamp/plugins" \ --set pluginsManager.enabled=true # Wait for pod to be ready -kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s +kubectl -n wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s ``` ## RBAC Requirements @@ -112,7 +112,7 @@ The plugin requires permissions to access the Polaris dashboard via Kubernetes s ```bash # Test if Headlamp service account has permission kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index aacc5b3..5835c07 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -38,7 +38,7 @@ EOF # Update Headlamp helm upgrade --install headlamp headlamp/headlamp \ - --namespace kube-system \ + --namespace \ --values headlamp-values.yaml ``` @@ -70,7 +70,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -111,7 +111,7 @@ EOF ```bash # Verify plugin files exist -kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \ +kubectl -n exec -it deployment/headlamp -c headlamp -- \ ls /headlamp/plugins/headlamp-polaris-plugin/dist/ # Expected output: @@ -119,7 +119,7 @@ kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \ # Verify RBAC is correct kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard @@ -185,7 +185,7 @@ Cluster score badge in top navigation: ```bash # Verify plugin files exist -kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \ +kubectl -n exec -it deployment/headlamp -c headlamp -- \ ls /headlamp/plugins/headlamp-polaris-plugin/ # If missing, reinstall via Headlamp UI or sidecar method diff --git a/docs/troubleshooting/README.md b/docs/troubleshooting/README.md index 44c128a..538015c 100644 --- a/docs/troubleshooting/README.md +++ b/docs/troubleshooting/README.md @@ -38,17 +38,17 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy # 3. Verify RBAC permissions kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard # Expected output: yes # 4. Check Headlamp pod is running -kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp +kubectl -n get pods -l app.kubernetes.io/name=headlamp # 5. Check Headlamp logs for plugin errors -kubectl -n kube-system logs deployment/headlamp | grep -i polaris +kubectl -n logs deployment/headlamp | grep -i polaris # Expected: No errors ``` @@ -57,7 +57,7 @@ kubectl -n kube-system logs deployment/headlamp | grep -i polaris ```bash # Verify plugin files exist -kubectl -n kube-system exec deployment/headlamp -c headlamp -- \ +kubectl -n exec deployment/headlamp -c headlamp -- \ ls -la /headlamp/plugins/headlamp-polaris-plugin/ # Expected output: @@ -76,7 +76,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy # Test permission (service account mode) kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard diff --git a/docs/troubleshooting/common-issues.md b/docs/troubleshooting/common-issues.md index 5201500..6dd8336 100644 --- a/docs/troubleshooting/common-issues.md +++ b/docs/troubleshooting/common-issues.md @@ -33,7 +33,7 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug ```bash # View Headlamp pod logs (plugin sidecar) -kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin +kubectl logs -n deployment/headlamp -c headlamp-plugin # Expected output: # Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz @@ -43,7 +43,7 @@ kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin **Verify plugin files exist**: ```bash -kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/ +kubectl exec -n deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/ # Should show: headlamp-polaris-plugin/ ``` @@ -118,7 +118,7 @@ Expected subjects: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: ``` For OIDC mode: @@ -154,7 +154,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -169,7 +169,7 @@ Service account mode: ```bash # Impersonate Headlamp service account kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ --resource-name=polaris-dashboard \ -n polaris # Expected: yes @@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \ After applying RBAC changes: ```bash -kubectl rollout restart deployment headlamp -n kube-system +kubectl rollout restart deployment headlamp -n ``` --- @@ -490,7 +490,7 @@ Run this script to test all RBAC components: #!/bin/bash NS="polaris" SA="headlamp" -SA_NS="kube-system" +SA_NS="" echo "=== Testing RBAC for Polaris Plugin ===" @@ -529,8 +529,8 @@ echo "=== Test complete ===" Test connectivity from Headlamp to Polaris: ```bash -# Create debug pod in kube-system namespace -kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash +# Create debug pod in the namespace where Headlamp is installed +kubectl run netdebug -n --rm -it --image=nicolaka/netshoot -- bash # Inside pod, test DNS and HTTP nslookup polaris-dashboard.polaris.svc.cluster.local @@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests: ```bash # View recent audit logs (location varies by cluster) -kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard +kubectl logs -n kube-apiserver-* | grep polaris-dashboard # Look for lines with: # "reason": "Forbidden" -# "user": "system:serviceaccount:kube-system:headlamp" +# "user": "system:serviceaccount::headlamp" ``` --- @@ -567,7 +567,7 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard **Check sidecar logs**: ```bash -kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin +kubectl logs -n deployment/headlamp -c headlamp-plugin ``` **Common errors**: @@ -591,7 +591,7 @@ Error: 404 Not Found **Solution**: Verify `archive-url` in plugin config matches GitHub release: ```bash -kubectl get configmap headlamp-plugin-config -n kube-system -o yaml +kubectl get configmap headlamp-plugin-config -n -o yaml ``` Expected format: @@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue: 1. **Version Information**: ```bash - kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image: + kubectl get pods -n -l app.kubernetes.io/name=headlamp -o yaml | grep image: ``` 2. **Plugin Version**: - Check Settings → Plugins in Headlamp UI - - Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json` + - Or: `kubectl exec -n deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json` 3. **Browser Console Output**: @@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue: 5. **Pod Logs**: ```bash - kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100 + kubectl logs -n deployment/headlamp -c headlamp --tail=100 kubectl logs -n polaris deployment/polaris-dashboard --tail=100 ``` diff --git a/docs/troubleshooting/rbac-issues.md b/docs/troubleshooting/rbac-issues.md index 92315d1..808c943 100644 --- a/docs/troubleshooting/rbac-issues.md +++ b/docs/troubleshooting/rbac-issues.md @@ -43,7 +43,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -83,7 +83,7 @@ roleRef: ```bash # Test service account (in-cluster mode) kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index b0d3bf0..829bcaf 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -317,7 +317,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy # Test permission kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard ``` diff --git a/docs/user-guide/rbac-permissions.md b/docs/user-guide/rbac-permissions.md index af58610..d3c2650 100644 --- a/docs/user-guide/rbac-permissions.md +++ b/docs/user-guide/rbac-permissions.md @@ -65,7 +65,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp # Adjust to your Headlamp SA name - namespace: kube-system # Adjust to Headlamp's namespace + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -75,7 +75,7 @@ roleRef: **Adjust for your environment:** - `subjects[0].name` - Your Headlamp service account name (often `headlamp`) -- `subjects[0].namespace` - Namespace where Headlamp runs (often `kube-system`) +- `subjects[0].namespace` - Namespace where Headlamp is installed ### Step 3: Apply and Verify @@ -91,7 +91,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy # Test permission kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard @@ -109,7 +109,7 @@ In token-auth mode, **each user's own identity** is used for Kubernetes API requ With service account mode: - Single RoleBinding grants access to all Headlamp users -- Kubernetes sees all requests as `system:serviceaccount:kube-system:headlamp` +- Kubernetes sees all requests as `system:serviceaccount::headlamp` With token-auth mode: @@ -267,7 +267,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -281,7 +281,7 @@ metadata: subjects: - kind: ServiceAccount name: headlamp - namespace: kube-system + namespace: roleRef: kind: Role name: polaris-proxy-reader @@ -411,7 +411,7 @@ Every plugin data fetch creates a Kubernetes API audit log entry. "level": "Metadata", "verb": "get", "user": { - "username": "system:serviceaccount:kube-system:headlamp" + "username": "system:serviceaccount::headlamp" }, "sourceIPs": ["10.96.0.1"], "objectRef": { @@ -494,7 +494,7 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do ```bash # Service account mode kubectl auth can-i get services/proxy \ - --as=system:serviceaccount:kube-system:headlamp \ + --as=system:serviceaccount::headlamp \ -n polaris \ --resource-name=polaris-dashboard -- 2.52.0 From a781027d3b2f295eaacf832e3df25b1d8f853bf8 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Mon, 11 May 2026 01:15:39 +0000 Subject: [PATCH 3/6] =?UTF-8?q?Remove=20all=20E2E=20infrastructure=20?= =?UTF-8?q?=E2=80=94=20approach=20is=20dead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete the entire local E2E testing setup: - e2e/ directory (Playwright tests) - scripts/deploy-e2e-headlamp.sh and teardown-e2e-headlamp.sh - .github/workflows/e2e.yaml - deployment/ (RBAC files and PLUGIN_LOADING_FIX.md) - playwright.config.ts - E2E npm scripts and @playwright/test dependency - E2E-related .gitignore entries RBAC is managed by Flux GitOps in privilegedescalation/infra. Co-Authored-By: Paperclip --- .github/workflows/e2e.yaml | 201 ------------------- .gitignore | 4 - deployment/PLUGIN_LOADING_FIX.md | 58 ------ deployment/e2e-ci-runner-rbac.yaml | 74 ------- deployment/polaris-rbac.yaml | 28 --- e2e/README.md | 303 ----------------------------- e2e/appbar.spec.ts | 90 --------- e2e/auth.setup.ts | 83 -------- e2e/polaris.spec.ts | 110 ----------- e2e/settings.spec.ts | 90 --------- package.json | 5 +- playwright.config.ts | 27 --- scripts/deploy-e2e-headlamp.sh | 210 -------------------- scripts/teardown-e2e-headlamp.sh | 34 ---- 14 files changed, 1 insertion(+), 1316 deletions(-) delete mode 100644 .github/workflows/e2e.yaml delete mode 100644 deployment/PLUGIN_LOADING_FIX.md delete mode 100644 deployment/e2e-ci-runner-rbac.yaml delete mode 100644 deployment/polaris-rbac.yaml delete mode 100644 e2e/README.md delete mode 100644 e2e/appbar.spec.ts delete mode 100644 e2e/auth.setup.ts delete mode 100644 e2e/polaris.spec.ts delete mode 100644 e2e/settings.spec.ts delete mode 100644 playwright.config.ts delete mode 100755 scripts/deploy-e2e-headlamp.sh delete mode 100755 scripts/teardown-e2e-headlamp.sh diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml deleted file mode 100644 index 688cae3..0000000 --- a/.github/workflows/e2e.yaml +++ /dev/null @@ -1,201 +0,0 @@ -name: E2E Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -# Only one E2E run at a time: the shared E2E_RELEASE (headlamp-e2e) in -# headlamp-dev cannot be shared across concurrent runs. -# cancel-in-progress: false (queue, don't cancel) — cancelling in-flight -# runs may skip the if:always() teardown, leaving dangling cluster resources. -concurrency: - group: e2e-${{ github.repository }} - cancel-in-progress: false - -env: - E2E_NAMESPACE: headlamp-dev - E2E_RELEASE: headlamp-e2e - # Pin to a known-good Headlamp version. Using :latest is risky because - # the tag can change between CI runs, causing flaky failures when a newer - # image is pulled on some nodes but not others (IfNotPresent pull policy). - # Update this when Headlamp is upgraded in production (kube-system). - HEADLAMP_VERSION: v0.40.1 - -jobs: - e2e: - runs-on: runners-privilegedescalation - timeout-minutes: 15 - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - cache: 'npm' - - - name: Setup kubectl - uses: azure/setup-kubectl@v4 - - - name: Get kubeconfig - run: | - set -euo pipefail - echo "=== Runner environment diagnostic ===" - echo "HOME=${HOME:-}" - echo "KUBECONFIG=${KUBECONFIG:-}" - echo "ACTIONS_KUBECONFIG=${ACTIONS_KUBECONFIG:-}" - echo "RUNNER_CONFIG=${RUNNER_CONFIG:-}" - echo "RUNNER_CONFIG_DIR=${RUNNER_CONFIG_DIR:-}" - echo "" - echo "=== Checking known kubeconfig locations ===" - for path in /runner/config /home/runner/.kube/config "${HOME:-}/.kube/config" "${HOME:-}/.kube"; do - if [ -f "$path" ]; then - echo "FOUND kubeconfig at: $path" - elif [ -d "$path" ]; then - echo "DIR exists at: $path, contents:" - ls -la "$path" 2>&1 || echo " (cannot list)" - else - echo "NOT FOUND: $path" - fi - done - echo "" - echo "=== In-cluster service account check ===" - in_cluster=false - if [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then - echo "Service account token present — in-cluster mode available" - echo "KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-}" - echo "KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-}" - in_cluster=true - else - echo "No service account token at /var/run/secrets/kubernetes.io/serviceaccount/" - fi - echo "" - if [ -f /runner/config ]; then - echo "KUBECONFIG=/runner/config" >> "$GITHUB_ENV" - echo "Using kubeconfig from /runner/config" - elif [ -f /home/runner/.kube/config ]; then - echo "KUBECONFIG=/home/runner/.kube/config" >> "$GITHUB_ENV" - echo "Using kubeconfig from /home/runner/.kube/config" - elif [ -f "${HOME:-}/.kube/config" ]; then - echo "KUBECONFIG=${HOME:-}/.kube/config" >> "$GITHUB_ENV" - echo "Using kubeconfig from HOME" - elif [ "$in_cluster" = true ]; then - echo "No static kubeconfig found — generating in-cluster kubeconfig" - KUBECFG_DIR="${HOME:-}/.kube" - mkdir -p "$KUBECFG_DIR" - kubectl config set-cluster in-cluster \ - --server="https://${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}:${KUBERNETES_SERVICE_PORT:-443}" \ - --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ - --embed-certs=true \ - --kubeconfig="$KUBECFG_DIR/config" 2>&1 - kubectl config set-credentials in-cluster \ - --token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ - --kubeconfig="$KUBECFG_DIR/config" 2>&1 - kubectl config set-context in-cluster \ - --cluster=in-cluster \ - --user=in-cluster \ - --kubeconfig="$KUBECFG_DIR/config" 2>&1 - kubectl config use-context in-cluster \ - --kubeconfig="$KUBECFG_DIR/config" 2>&1 - echo "KUBECONFIG=$KUBECFG_DIR/config" >> "$GITHUB_ENV" - echo "Generated in-cluster kubeconfig at $KUBECFG_DIR/config" - else - echo "::error::No kubeconfig found in /runner/config, /home/runner/.kube/config, HOME, or in-cluster service account" - exit 1 - fi - - - name: Apply RBAC for E2E pipeline - run: | - set -x - kubectl apply -f deployment/e2e-ci-runner-rbac.yaml --dry-run=server 2>&1 || true - kubectl apply -f deployment/e2e-ci-runner-rbac.yaml 2>&1 - echo "exit code: $?" - echo "Waiting for RBAC propagation..." - sleep 5 - echo "Verifying RBAC resources were created..." - kubectl get role e2e-ci-runner -n headlamp-dev 2>&1 | tail -3 - kubectl get role e2e-ci-runner-polaris -n headlamp-dev 2>&1 | tail -3 - kubectl get rolebinding e2e-ci-runner-binding -n headlamp-dev 2>&1 | tail -3 - set +x - - - name: Apply Polaris dashboard RBAC - run: kubectl apply -f deployment/polaris-rbac.yaml - - - name: RBAC pre-flight check - run: | - echo "Checking RBAC resources..." - MISSING=0 - kubectl get role polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 - kubectl get rolebinding polaris-dashboard-proxy-reader -n polaris -o name >/dev/null 2>&1 || MISSING=1 - kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" 2>/dev/null || MISSING=1 - if [ "$MISSING" -eq 0 ]; then - echo "RBAC pre-flight check passed." - else - echo "::error::RBAC pre-flight check failed. Missing required permissions." - exit 1 - fi - - - name: Install dependencies - run: npm ci - - - 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: npx playwright install --with-deps chromium - - - name: Run E2E tests - run: npm run e2e - 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 diff --git a/.gitignore b/.gitignore index a022014..4b055d8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,6 @@ node_modules/ dist/ .headlamp-plugin/ *.tar.gz -e2e/.auth/ -test-results/ -.playwright-mcp/ .env -.env.e2e .env.local .eslintcache diff --git a/deployment/PLUGIN_LOADING_FIX.md b/deployment/PLUGIN_LOADING_FIX.md deleted file mode 100644 index e176a2b..0000000 --- a/deployment/PLUGIN_LOADING_FIX.md +++ /dev/null @@ -1,58 +0,0 @@ -# Headlamp Plugin Loading Issue - Root Cause and Fix - -## Problem -Headlamp v0.39.0 was not loading plugins installed via the plugin manager. Plugins appeared in Settings → Plugins but: -- No sidebar entries appeared -- No plugin settings were available -- Plugin JavaScript was not being executed in the browser - -## Root Cause -When `config.watchPlugins: true` (the default), Headlamp treats catalog-managed plugins in `/headlamp/plugins/` as "development directory" plugins. This causes: -- Backend serves plugin metadata correctly -- Backend logs show "Treating catalog-installed plugin in development directory as user plugin" -- **Frontend does NOT execute the plugin JavaScript** -- Plugin registrations (`registerSidebarEntry`, `registerRoute`, etc.) never happen - -## Solution -Set `config.watchPlugins: false` in the Headlamp HelmRelease values: - -```yaml -spec: - values: - config: - watchPlugins: false - pluginsManager: - enabled: true - configContent: | - plugins: - - name: polaris - source: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin - # ... other plugins -``` - -## Why This Works -With `watchPlugins: false`: -- Headlamp no longer treats catalog-managed plugins as "development" plugins -- Frontend properly loads and executes plugin JavaScript on startup -- Plugin registrations happen correctly -- All plugin features (sidebar, routes, settings, etc.) work as expected - -## Testing -After applying this fix: -1. Verify plugins are installed: `kubectl logs -n kube-system -c headlamp-plugin` -2. Verify watchPlugins is false: `kubectl logs -n kube-system -c headlamp | grep "Watch Plugins"` -3. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) to clear cached JavaScript -4. Verify plugin sidebar entries appear -5. Verify plugin functionality works - -## Additional Notes -- This appears to be a bug/limitation in Headlamp v0.39.0 -- The `watchPlugins` feature is intended for development scenarios where plugins are being actively modified -- For production deployments with catalog-managed plugins, `watchPlugins: false` is the correct configuration -- Once plugins are loaded, subsequent restarts or updates work correctly as long as `watchPlugins` remains false - -## References -- Headlamp Helm Chart: https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp -- Plugin Manager: https://github.com/headlamp-k8s/headlamp/tree/main/plugins/headlamp-plugin -- Issue discovered: 2026-02-11 -- Fix applied: 2026-02-12 diff --git a/deployment/e2e-ci-runner-rbac.yaml b/deployment/e2e-ci-runner-rbac.yaml deleted file mode 100644 index 069c5ee..0000000 --- a/deployment/e2e-ci-runner-rbac.yaml +++ /dev/null @@ -1,74 +0,0 @@ ---- -# RBAC for the GitHub Actions CI runner to manage the E2E Headlamp instance. -# CI-only test fixture — NOT for production use. -# -# Grants the ARC runner service account permissions in the headlamp-dev -# namespace to deploy and tear down a dedicated Headlamp instance via Helm. -# E2E resources run in `headlamp-dev` — nothing persists beyond a test run. -# -# Plugin is loaded via ConfigMap volume mount — no custom Docker images. -# -# Note: This RBAC is mirrored in privilegedescalation/infra (base/rbac/) -# and managed by Flux GitOps. The infra repo is the source of truth. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: e2e-ci-runner - namespace: headlamp-dev -rules: - # Helm needs to manage these resources for the Headlamp chart - - apiGroups: ["apps"] - resources: ["deployments"] - verbs: ["get", "list", "create", "update", "patch", "delete", "watch"] - - apiGroups: [""] - resources: ["services", "serviceaccounts", "configmaps", "secrets", "events"] - verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - # Token creation for E2E test auth - - apiGroups: [""] - resources: ["serviceaccounts/token"] - verbs: ["create"] - # Apply Polaris dashboard RBAC in the polaris namespace - - apiGroups: ["rbac.authorization.k8s.io"] - resources: ["roles", "rolebindings"] - verbs: ["get", "list", "create", "update", "patch", "delete"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: e2e-ci-runner-polaris - namespace: polaris -rules: - - apiGroups: ["rbac.authorization.k8s.io"] - resources: ["roles", "rolebindings"] - verbs: ["get", "list", "create", "update", "patch", "delete"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: e2e-ci-runner-polaris - namespace: polaris -subjects: - - kind: ServiceAccount - name: runners-privilegedescalation-gha-rs-no-permission - namespace: arc-runners -roleRef: - kind: Role - name: e2e-ci-runner-polaris - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: e2e-ci-runner-binding - namespace: headlamp-dev -subjects: - - kind: ServiceAccount - name: runners-privilegedescalation-gha-rs-no-permission - namespace: arc-runners -roleRef: - kind: Role - name: e2e-ci-runner - apiGroup: rbac.authorization.k8s.io diff --git a/deployment/polaris-rbac.yaml b/deployment/polaris-rbac.yaml deleted file mode 100644 index a3b3629..0000000 --- a/deployment/polaris-rbac.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# RBAC to allow authenticated users to proxy to the Polaris dashboard service. -# The polaris plugin reads audit data via the Kubernetes service proxy: -# /api/v1/namespaces/polaris/services/http:polaris-dashboard:80/proxy/results.json -# Without this Role + RoleBinding, users get a 403 when Headlamp proxies the request. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: polaris-dashboard-proxy-reader - namespace: polaris -rules: - - apiGroups: [""] - resources: ["services/proxy"] - resourceNames: ["polaris-dashboard", "http:polaris-dashboard:80"] - verbs: ["get"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: polaris-dashboard-proxy-reader - namespace: polaris -subjects: - - kind: Group - name: system:authenticated - apiGroup: rbac.authorization.k8s.io -roleRef: - kind: Role - name: polaris-dashboard-proxy-reader - apiGroup: rbac.authorization.k8s.io diff --git a/e2e/README.md b/e2e/README.md deleted file mode 100644 index b93d390..0000000 --- a/e2e/README.md +++ /dev/null @@ -1,303 +0,0 @@ -# E2E Smoke Tests - -Playwright-based smoke tests that validate the Polaris plugin against a live Headlamp deployment. - -## CI - -E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`): - -1. Builds the plugin (`npm run build`) -2. Creates a ConfigMap from the built `dist/` output -3. Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume -4. Generates a ServiceAccount token for test auth -5. Runs Playwright tests against the E2E instance -6. Tears down the E2E instance - -This approach uses the stock `ghcr.io/headlamp-k8s/headlamp` image with no custom Docker builds. The plugin is loaded via `HEADLAMP_PLUGINS_DIR` volume mount. - -### Required GitHub Secrets - -Configure these in GitHub repository settings (Settings → Secrets and variables → Actions): - -| Secret | Required | Description | -| -------------------- | -------- | -------------------------------------------------------------- | -| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access | -| `AUTHENTIK_PASSWORD` | OIDC | Password for that user | - -Token-based auth is auto-generated by the deploy script. OIDC secrets are only needed if testing against the shared Headlamp instance. - -No `GHCR_TOKEN` or Docker registry secrets are needed — the stock Headlamp image is public. - -## Running Locally - -### Option 1: OIDC via Authentik (same as CI) - -```bash -AUTHENTIK_USERNAME=you@example.com AUTHENTIK_PASSWORD=... npm run e2e -``` - -The default base URL is `https://headlamp.animaniacs.farh.net`. Override with `HEADLAMP_URL` if needed. - -### Option 2: K8s bearer token (port-forward) - -```bash -kubectl port-forward -n kube-system svc/headlamp 4466:80 -export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system) -HEADLAMP_URL=http://localhost:4466 npm run e2e -``` - -Or in headed mode (opens a browser window): - -```bash -HEADLAMP_URL=http://localhost:4466 npm run e2e:headed -``` - -## Environment Variables - -| Variable | Required | Default | Description | -| -------------------- | -------- | -------------------------------------- | --------------------------------------- | -| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance | -| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username | -| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password | -| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (auto-generated in CI) | - -In CI, `HEADLAMP_URL` and `HEADLAMP_TOKEN` are set automatically by the deploy script. For local runs, set either OIDC credentials or a token manually. - -## What the Tests Validate - -- **Sidebar entry** — The Polaris sidebar item appears after login -- **Overview page** — Cluster score and check distribution render correctly -- **Namespaces page** — Table of namespaces loads with clickable links -- **Namespace detail** — Clicking a namespace shows its score and resource table - -These are smoke tests against real cluster data. They verify the plugin loads and renders without errors, not specific data values. - -## Test Coverage - -### Current Tests (`polaris.spec.ts`) - -1. **`sidebar contains Polaris entry`** - - Verifies Polaris appears in the navigation sidebar - - Ensures plugin successfully registered sidebar entry - -2. **`overview page renders cluster score`** - - Navigates to `/c/main/polaris` - - Checks for "Polaris — Overview" heading - - Verifies cluster score percentage is displayed - - Validates data fetching and rendering - -3. **`namespaces page renders table with namespace buttons`** - - Navigates to `/c/main/polaris/namespaces` - - Checks for "Polaris — Namespaces" heading - - Verifies table is visible with at least one row - - Ensures namespace buttons are clickable - -4. **`namespace detail drawer opens from table button`** - - Clicks first namespace button in table - - Verifies drawer opens with namespace name in heading - - Checks "Namespace Score" section is visible - - Confirms "Resources" table is displayed - - Validates URL hash is updated with namespace name - -5. **`namespace detail drawer closes with Escape key`** - - Opens namespace drawer - - Presses Escape key - - Verifies drawer closes - - Checks URL hash is cleared - -6. **`namespace detail drawer opens from URL hash`** - - Navigates directly to `/c/main/polaris/namespaces#` - - Verifies drawer automatically opens - - Checks namespace details are displayed - -## Prerequisites - -### Cluster Requirements - -1. **Polaris Deployment** - ```bash - # Verify Polaris is running - kubectl -n polaris get pods - kubectl -n polaris get svc polaris-dashboard - ``` - -2. **Polaris Audit Data** - ```bash - # Check if Polaris has generated audit results - kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime' - ``` - -3. **RBAC Permissions** - - Headlamp service account (or test user) needs `get` on `services/proxy` for `polaris-dashboard` - - See main README for RBAC setup - -### Local Setup - -```bash -# 1. Install dependencies -npm install -npx playwright install chromium - -# 2. Create .env file (optional, for persistent config) -cp .env.example .env - -# 3. Set environment variables -export HEADLAMP_URL=https://your-headlamp-instance.com -export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system) - -# 4. Run tests -npm run e2e -``` - -## Debugging - -### Run in Headed Mode - -See the browser UI while tests run: - -```bash -npm run e2e:headed -``` - -### Enable Debug Mode - -Step through tests with Playwright Inspector: - -```bash -npx playwright test --debug -``` - -### Generate Trace - -Record full trace for failed tests: - -```bash -npx playwright test --trace on -npx playwright show-trace test-results//trace.zip -``` - -### Screenshot on Failure - -Tests automatically capture screenshots on failure in `test-results/` - -### Common Issues - -**Auth fails with "Sign In button not found":** -- Check HEADLAMP_URL is correct -- Verify Headlamp is accessible -- Ensure OIDC is configured if using Authentik - -**Polaris sidebar entry not found:** -- Plugin may not be installed: Check Settings → Plugins in Headlamp -- Plugin may have failed to load: Check browser console -- Clear browser cache and hard refresh - -**Cluster score not displayed:** -- Polaris may not have audit data yet -- Check Polaris is running: `kubectl -n polaris get pods` -- Verify service proxy: `kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json` - -**Namespace table empty:** -- Polaris hasn't run audit yet (wait a few minutes) -- Check Polaris logs: `kubectl -n polaris logs -l app.kubernetes.io/name=polaris` - -## Writing New Tests - -### Example: Testing Plugin Settings - -```typescript -test('plugin settings page shows Polaris configuration', async ({ page }) => { - await page.goto('/c/main/settings/plugins'); - - // Find and click Polaris plugin - await page.getByText('headlamp-polaris-plugin').click(); - - // Check settings are visible - await expect(page.getByText('Polaris Settings')).toBeVisible(); - await expect(page.getByText('Refresh Interval')).toBeVisible(); - await expect(page.getByText('Dashboard URL')).toBeVisible(); -}); -``` - -### Example: Testing App Bar Badge - -```typescript -test('app bar displays Polaris score badge', async ({ page }) => { - await page.goto('/c/main'); - - // Badge should be visible in app bar - const badge = page.getByRole('button', { name: /Polaris: \d+%/ }); - await expect(badge).toBeVisible(); - - // Clicking should navigate to overview - await badge.click(); - await expect(page).toHaveURL(/\/c\/main\/polaris$/); -}); -``` - -### Example: Testing Dark Mode - -```typescript -test('plugin UI adapts to dark mode', async ({ page }) => { - await page.goto('/c/main/polaris'); - - // Toggle dark mode - await page.getByRole('button', { name: /theme/i }).click(); - - // Check background color changes - const body = page.locator('body'); - await expect(body).toHaveCSS('background-color', 'rgb(18, 18, 18)'); - - // Plugin components should adapt - const sectionBox = page.locator('[class*="MuiPaper"]').first(); - await expect(sectionBox).not.toHaveCSS('background-color', 'rgb(255, 255, 255)'); -}); -``` - -## CI/CD Integration - -Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration. - -### Architecture - -The E2E workflow deploys a **dedicated Headlamp instance** for each test run: - -1. Build plugin (`npm run build`) -2. Create ConfigMap from `dist/` output (`scripts/deploy-e2e-headlamp.sh`) -3. Deploy stock Headlamp via Helm with ConfigMap volume mount -4. Run Playwright tests against the E2E instance -5. Tear down (`scripts/teardown-e2e-headlamp.sh`) - -No custom Docker images, no PVCs, no kubectl exec/cp, no patching of existing deployments. The plugin is mounted from a ConfigMap into the stock Headlamp image. - -### Cluster Prerequisites - -One-time setup by a cluster admin: - -```bash -kubectl apply -f deployment/e2e-ci-runner-rbac.yaml -``` - -### Manual Trigger - -You can manually trigger E2E tests from GitHub Actions: -1. Go to Actions → E2E Tests -2. Click "Run workflow" -3. Select branch and run - -## Best Practices - -1. **Use semantic selectors**: `getByRole`, `getByText` over CSS selectors -2. **Wait for visibility**: Use `await expect(...).toBeVisible()` instead of `waitForTimeout` -3. **Keep tests independent**: Each test should work in isolation -4. **Test user flows**: Complete journeys, not just page loads -5. **Clean up state**: Close drawers/modals after tests -6. **Use storage state**: Reuse auth across tests (already configured) -7. **Parallelize carefully**: Currently disabled due to shared state - -## Resources - -- [Playwright Documentation](https://playwright.dev/) -- [Playwright Best Practices](https://playwright.dev/docs/best-practices) -- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/) -- [Project Main README](../README.md) diff --git a/e2e/appbar.spec.ts b/e2e/appbar.spec.ts deleted file mode 100644 index ccb4788..0000000 --- a/e2e/appbar.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Polaris app bar badge', () => { - test('badge displays cluster score in app bar', async ({ page }) => { - await page.goto('/c/main'); - - // Wait for page to load - await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible(); - - // Badge should be visible in app bar with score percentage - const badge = page.getByRole('button', { name: /Polaris: \d+%/ }); - await expect(badge).toBeVisible({ timeout: 15_000 }); - - // Badge should show shield emoji - await expect(badge).toContainText('🛡️'); - }); - - test('clicking badge navigates to overview page', async ({ page }) => { - await page.goto('/c/main'); - - // Find and click the badge - const badge = page.getByRole('button', { name: /Polaris: \d+%/ }); - await expect(badge).toBeVisible({ timeout: 15_000 }); - await badge.click(); - - // Should navigate to Polaris overview - await expect(page).toHaveURL(/\/c\/main\/polaris$/); - await expect(page.getByRole('heading', { name: 'Polaris — Overview' })).toBeVisible(); - }); - - test('badge color reflects score level', async ({ page }) => { - await page.goto('/c/main'); - - // Get the badge - const badge = page.getByRole('button', { name: /Polaris: \d+%/ }); - await expect(badge).toBeVisible({ timeout: 15_000 }); - - // Extract score from button text - const badgeText = await badge.textContent(); - const scoreMatch = badgeText?.match(/(\d+)%/); - expect(scoreMatch).toBeTruthy(); - - const score = parseInt(scoreMatch![1]); - - // Check background color matches score level - const bgColor = await badge.evaluate(el => - window.getComputedStyle(el).backgroundColor - ); - - // Verify that the badge has a non-default background color applied - // (theme-dependent RGB values vary across Headlamp versions, so we - // only assert that a real color is set rather than transparent/default) - expect(bgColor).not.toBe('rgba(0, 0, 0, 0)'); - expect(bgColor).not.toBe('transparent'); - expect(bgColor).toMatch(/^rgb/); - }); - - test('badge updates when navigating between clusters', async ({ page }) => { - // This test assumes multi-cluster setup; skip if only one cluster - await page.goto('/c/main'); - - // Get initial badge score - const badge = page.getByRole('button', { name: /Polaris: \d+%/ }); - await expect(badge).toBeVisible({ timeout: 15_000 }); - const initialScore = await badge.textContent(); - - // Try to switch clusters (if available) - const clusterSelector = page.getByRole('button', { name: /cluster/i }); - if (await clusterSelector.isVisible()) { - // Note: This part will only work in multi-cluster setups - // For single-cluster, this test will just verify badge persists - await clusterSelector.click(); - - // Select different cluster if available - const clusterOptions = page.getByRole('menuitem'); - const count = await clusterOptions.count(); - - if (count > 1) { - await clusterOptions.nth(1).click(); - - // Badge should update or disappear (if new cluster doesn't have Polaris) - // This is just verifying no crash occurs - await page.waitForTimeout(2000); - } - } - - // Badge should still be functional - await expect(badge).toBeEnabled(); - }); -}); diff --git a/e2e/auth.setup.ts b/e2e/auth.setup.ts deleted file mode 100644 index 2b4ecb9..0000000 --- a/e2e/auth.setup.ts +++ /dev/null @@ -1,83 +0,0 @@ -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 { - // Navigate to login — Headlamp redirects / to /c/main/login - await page.goto('/'); - await page.waitForURL('**/login'); - - // Click "Sign In" and capture the Authentik popup - const popupPromise = page.waitForEvent('popup'); - await page.getByRole('button', { name: /sign in/i }).click(); - const popup = await popupPromise; - - // Wait for the Authentik popup to fully load before interacting - await popup.waitForLoadState('domcontentloaded'); - await popup.waitForLoadState('networkidle'); - - // Authentik step 1: fill username — wait for the form to render - 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(); - - // Authentik step 2: fill password — wait for the next step to load - 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(); - - // Wait for the popup to close (Authentik redirects back, Headlamp processes callback) - await popup.waitForEvent('close', { timeout: 15_000 }); - - // Original page should now be authenticated — wait for sidebar - await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({ - timeout: 15_000, - }); -} - -async function authenticateWithToken(page: Page, token: string): Promise { - await page.goto('/'); - // Headlamp goes to /token directly when no OIDC is configured, - // or through /login when OIDC is configured - await page.waitForURL(/\/(login|token)$/); - - if (page.url().includes('/login')) { - // OIDC login page — click "use a token" to reach token auth. - // Wait explicitly before clicking so failures surface at 15 s - // with a clear message rather than silently timing out at 60 s. - 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'); - } - - // Fill the "ID token" field and submit - await page.getByRole('textbox', { name: /id token/i }).fill(token); - await page.getByRole('button', { name: /authenticate/i }).click(); - - // Wait for the main UI to load - 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 }); -}); diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts deleted file mode 100644 index 1d6df99..0000000 --- a/e2e/polaris.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Polaris plugin smoke tests', () => { - test('sidebar contains Polaris entry', async ({ page }) => { - await page.goto('/'); - // The sidebar is the "Navigation" nav element (not "Appbar Tools") - const sidebar = page.getByRole('navigation', { name: 'Navigation' }); - await expect(sidebar).toBeVisible({ timeout: 15_000 }); - await expect(sidebar.getByRole('button', { name: 'Polaris' })).toBeVisible(); - }); - - test('overview page renders cluster score', async ({ page }) => { - await page.goto('/c/main/polaris'); - - // SectionHeader renders a heading - await expect(page.getByRole('heading', { name: 'Polaris \u2014 Overview' })).toBeVisible(); - - // "Cluster Score" section exists with a percentage - await expect(page.getByText('Cluster Score')).toBeVisible(); - await expect(page.locator('main').getByText(/%/).first()).toBeVisible(); - }); - - test('namespaces page renders table with namespace buttons', async ({ page }) => { - await page.goto('/c/main/polaris/namespaces'); - - await expect(page.getByRole('heading', { name: 'Polaris \u2014 Namespaces' })).toBeVisible(); - - // Table should have at least one row with a namespace button - const table = page.locator('table'); - await expect(table).toBeVisible(); - const rows = table.locator('tbody tr'); - await expect(rows.first()).toBeVisible(); - - // Each namespace row should contain a button (now buttons instead of links for drawer) - const firstButton = rows.first().locator('button'); - await expect(firstButton).toBeVisible(); - }); - - test('namespace detail drawer opens from table button', async ({ page }) => { - await page.goto('/c/main/polaris/namespaces'); - - // Click the first namespace button in the table - const table = page.locator('table'); - await expect(table).toBeVisible(); - const firstButton = table.locator('tbody tr').first().locator('button'); - const namespaceName = await firstButton.textContent(); - await firstButton.click(); - - // Drawer should open and show the namespace name in the heading - await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) - ).toBeVisible(); - - // "Namespace Score" section should be present in drawer - await expect(page.getByText('Namespace Score')).toBeVisible(); - - // Resources table should exist in drawer - await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible(); - - // URL hash should be updated with namespace name - await expect(page).toHaveURL(/\/polaris\/namespaces#/); - }); - - test('namespace detail drawer closes with Escape key', async ({ page }) => { - await page.goto('/c/main/polaris/namespaces'); - - // Open the drawer by clicking a namespace button - const table = page.locator('table'); - await expect(table).toBeVisible(); - const firstButton = table.locator('tbody tr').first().locator('button'); - const namespaceName = await firstButton.textContent(); - await firstButton.click(); - - // Verify drawer is open - await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) - ).toBeVisible(); - - // Press Escape key - await page.keyboard.press('Escape'); - - // Drawer should close (heading should not be visible anymore) - await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) - ).not.toBeVisible(); - - // URL hash should be cleared - await expect(page).toHaveURL(/\/polaris\/namespaces$/); - }); - - test('namespace detail drawer opens from URL hash', async ({ page }) => { - // Get a namespace name first - await page.goto('/c/main/polaris/namespaces'); - const table = page.locator('table'); - await expect(table).toBeVisible(); - const firstButton = table.locator('tbody tr').first().locator('button'); - const namespaceName = await firstButton.textContent(); - - // Navigate directly to URL with hash - await page.goto(`/c/main/polaris/namespaces#${namespaceName}`); - - // Drawer should automatically open with the namespace details - await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) - ).toBeVisible(); - - // "Namespace Score" section should be present - await expect(page.getByText('Namespace Score')).toBeVisible(); - }); -}); diff --git a/e2e/settings.spec.ts b/e2e/settings.spec.ts deleted file mode 100644 index 333a0b4..0000000 --- a/e2e/settings.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { test, expect, Page } from '@playwright/test'; - -/** Navigate to the Polaris plugin settings page and wait for settings to render. */ -async function goToPolarisSettings(page: Page) { - // Headlamp's plugin settings page is a HOME-context route at /settings/plugins, - // not an in-cluster route (/c/main/settings/plugins would 404). Headlamp loads - // plugin scripts asynchronously on SPA init. When registerPluginSettings() fires, - // it dispatches a Redux action — PluginSettings uses useTypedSelector so it - // re-renders automatically once the plugin registers. No preloading needed. - await page.goto('/settings/plugins'); - - // Wait for the plugin to appear in the settings list. The timeout covers - // async plugin script loading + registration. - const pluginEntry = page.locator('text=headlamp-polaris').first(); - await expect(pluginEntry).toBeVisible({ timeout: 30_000 }); - await pluginEntry.click(); - - // Wait for the PolarisSettings component to render - await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 }); -} - -test.describe('Polaris plugin settings', () => { - test('settings page shows configuration options', async ({ page }) => { - await goToPolarisSettings(page); - - // SectionBox title should be visible - await expect(page.getByText('Polaris Settings')).toBeVisible(); - }); - - test('refresh interval setting is configurable', async ({ page }) => { - await goToPolarisSettings(page); - - // Find the refresh interval dropdown - const intervalSelect = page.locator('select').filter({ hasText: /minute|second/ }); - await expect(intervalSelect).toBeVisible(); - - // Get current value - const currentValue = await intervalSelect.inputValue(); - - // Change to a different value - const newValue = currentValue === '300' ? '600' : '300'; - await intervalSelect.selectOption(newValue); - - // Value should be updated - await expect(intervalSelect).toHaveValue(newValue); - }); - - test('dashboard URL setting is configurable', async ({ page }) => { - await goToPolarisSettings(page); - - // Find the dashboard URL input - const urlInput = page.getByPlaceholder(/polaris-dashboard/); - await expect(urlInput).toBeVisible(); - - // Input should have the default proxy URL or custom URL - const currentUrl = await urlInput.inputValue(); - expect(currentUrl).toBeTruthy(); - - // Examples text should be visible - await expect(page.getByText('Examples:')).toBeVisible(); - await expect(page.getByText(/K8s proxy:/)).toBeVisible(); - }); - - test('connection test button is available', async ({ page }) => { - await goToPolarisSettings(page); - - // Find and verify test connection button - const testButton = page.getByRole('button', { name: /test connection/i }); - await expect(testButton).toBeVisible(); - await expect(testButton).toBeEnabled(); - }); - - test('connection test works with valid URL', async ({ page }) => { - await goToPolarisSettings(page); - - // Click test connection - const testButton = page.getByRole('button', { name: /test connection/i }); - await testButton.click(); - - // Wait for either success or error message - // Note: This will succeed if Polaris is accessible, fail otherwise - await page.waitForSelector('text=/Connected successfully|Connection failed/', { - timeout: 15_000, - }); - - // Either success or failure is acceptable (depends on environment) - const result = await page.textContent('body'); - expect(result).toMatch(/(Connected successfully|Connection failed)/); - }); -}); diff --git a/package.json b/package.json index 059fa7d..9ed9baa 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,7 @@ "format": "prettier --write src/", "format:check": "prettier --check src/", "test": "vitest run", - "test:watch": "vitest", - "e2e": "playwright test", - "e2e:headed": "playwright test --headed" + "test:watch": "vitest" }, "peerDependencies": { "react": "^18.0.0", @@ -45,7 +43,6 @@ "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", diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 16808ba..0000000 --- a/playwright.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -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 || 'https://headlamp.animaniacs.farh.net', - 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'], - }, - ], -}); diff --git a/scripts/deploy-e2e-headlamp.sh b/scripts/deploy-e2e-headlamp.sh deleted file mode 100755 index 8314b7d..0000000 --- a/scripts/deploy-e2e-headlamp.sh +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env bash -# deploy-e2e-headlamp.sh -# -# Deploys a stock Headlamp instance with the polaris plugin loaded via -# a ConfigMap volume mount. No custom Docker images — the plugin is built -# in CI and injected as a ConfigMap. -# -# E2E resources are deployed to the `headlamp-dev` namespace. Nothing -# persists beyond a test run — teardown cleans up all created resources. -# -# Prerequisites: -# - Plugin built (dist/ exists with plugin-main.js + package.json) -# - kubectl configured with cluster access -# - RBAC applied (managed by Flux GitOps in privilegedescalation/infra) -# -# 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: v0.40.1, pinned to match production) -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:-v0.40.1}" - -if [ ! -d "$DIST_DIR" ]; then - echo "ERROR: dist/ not found. Run 'npm run build' first." >&2 - exit 1 -fi - -# --- Preflight: verify RBAC before touching the cluster --- -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 - exit 1 -fi - -echo "=== E2E Headlamp Deployment ===" -echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}" -echo " Namespace: $E2E_NAMESPACE" -echo " Release: $E2E_RELEASE" - -# --- Create ConfigMap from built plugin --- -echo "" -echo "Creating ConfigMap with plugin files..." - -# Delete existing ConfigMap if present (idempotent redeploy) -kubectl delete configmap headlamp-polaris-plugin \ - -n "$E2E_NAMESPACE" --ignore-not-found - -# Create ConfigMap from dist/ contents and package.json -kubectl create configmap headlamp-polaris-plugin \ - -n "$E2E_NAMESPACE" \ - --from-file="$DIST_DIR" \ - --from-file=package.json="$REPO_ROOT/package.json" - -# --- Tear down any existing E2E deployment for a clean start --- -# kubectl apply without prior deletion only patches in-place: if the pod spec is -# unchanged between runs, no new rollout is triggered and a degraded pod keeps -# serving. Delete first to guarantee a fresh pod regardless of prior state. -echo "" -echo "Removing any existing E2E deployment (clean-start)..." -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 - -# --- Deploy Headlamp via kubectl apply --- -echo "" -echo "Deploying Headlamp E2E instance..." - -kubectl apply -f - </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 " export HEADLAMP_URL=${SVC_URL}" - -# --- Generate a token for test auth --- -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 - - -TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "") -if [ -n "$TOKEN" ]; then - echo " export HEADLAMP_TOKEN=" - echo "" - 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. Set HEADLAMP_TOKEN manually or use OIDC." -fi - -echo "" -echo "E2E deployment complete." diff --git a/scripts/teardown-e2e-headlamp.sh b/scripts/teardown-e2e-headlamp.sh deleted file mode 100755 index 00d4f5a..0000000 --- a/scripts/teardown-e2e-headlamp.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/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-polaris-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 - -# Clean up local env file -rm -f "$REPO_ROOT/.env.e2e" - -echo "Teardown complete." -- 2.52.0 From 96145c21cbce5a46f9ff496a944b11060b1c9f85 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Mon, 11 May 2026 09:20:51 +0000 Subject: [PATCH 4/6] fix: update pnpm-lock.yaml after removing @playwright/test The lockfile was out of sync with package.json after playwright removal, causing CI to fail with --frozen-lockfile. Co-Authored-By: Paperclip --- pnpm-lock.yaml | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aff7413..92e2f19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: '@mui/material': specifier: ^5.15.14 version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(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@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@playwright/test': - specifier: ^1.58.2 - version: 1.58.2 '@testing-library/jest-dom': specifier: ^6.4.8 version: 6.9.1 @@ -876,11 +873,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.58.2': - resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} - engines: {node: '>=18'} - hasBin: true - '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -3048,11 +3040,6 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4258,16 +4245,6 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} - playwright-core@1.58.2: - resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.58.2: - resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} - engines: {node: '>=18'} - hasBin: true - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -6185,7 +6162,7 @@ snapshots: jsdom: 24.1.3 jsonpath-plus: 10.4.0 lodash: 4.18.1 - material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7) + material-react-table: 2.13.3(0078ddeddc9e779fa84c03996c1db10e) 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.2)) @@ -6592,10 +6569,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.58.2': - dependencies: - playwright: 1.58.2 - '@popperjs/core@2.11.8': {} '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': @@ -9099,9 +9072,6 @@ snapshots: fs.realpath@1.0.0: {} - fsevents@2.3.2: - optional: true - fsevents@2.3.3: optional: true @@ -9897,7 +9867,7 @@ snapshots: '@types/minimatch': 3.0.5 minimatch: 3.1.5 - material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7): + material-react-table@2.13.3(0078ddeddc9e779fa84c03996c1db10e): 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) @@ -10529,14 +10499,6 @@ snapshots: dependencies: find-up: 5.0.0 - playwright-core@1.58.2: {} - - playwright@1.58.2: - dependencies: - playwright-core: 1.58.2 - optionalDependencies: - fsevents: 2.3.2 - possible-typed-array-names@1.1.0: {} postcss-modules-extract-imports@3.1.0(postcss@8.5.8): -- 2.52.0 From 398e3f3b9594437d4997a60bc1048ed7702327b4 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-engineer[bot]" <269729446+privilegedescalation-engineer[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 17:23:29 +0000 Subject: [PATCH 5/6] docs: remove stale e2e command references from CLAUDE.md Removed lines 28-29 which listed ghost E2E commands (npm run e2e, npm run e2e:headed). The repo has no E2E files, no playwright.config.ts, no e2e/ directory, and no e2e script in package.json. Resolves: PRI-1147 Co-authored-by: Chris Farhood Co-authored-by: Paperclip --- CLAUDE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fd13c33..ad93696 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,8 +25,6 @@ npm run format:check # Prettier check npm test # vitest run npm run test:watch # vitest watch mode npx vitest run src/api/polaris.test.ts # run a single test file -npm run e2e # Playwright E2E tests -npm run e2e:headed # Playwright headed mode ``` All tests and `tsc` must pass before committing. -- 2.52.0 From 34ea1117766a857b6f6bc48ce24da51fc1824010 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-ceo[bot]" <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 21:40:07 +0000 Subject: [PATCH 6/6] Update CI and approval workflows for three-branch SDLC (#158) CI triggers on dev/uat/main. Promotion gate replaces dual-approval. Co-authored-by: Chris Farhood Co-authored-by: Paperclip --- .github/workflows/ci.yaml | 4 ++-- .github/workflows/dual-approval.yaml | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 899f2b1..654169d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [main, dev, uat] pull_request: - branches: [main] + branches: [main, dev, uat] workflow_dispatch: workflow_call: diff --git a/.github/workflows/dual-approval.yaml b/.github/workflows/dual-approval.yaml index c4a96cf..9552ee4 100644 --- a/.github/workflows/dual-approval.yaml +++ b/.github/workflows/dual-approval.yaml @@ -1,20 +1,21 @@ -name: Dual Approval (CTO + QA) +name: Promotion Gate -# Calls the shared dual-approval-check workflow. -# Passes when both privilegedescalation-cto and privilegedescalation-qa -# have approved the PR. Add "Dual Approval (CTO + QA)" to required_status_checks -# in branch protection to enforce this gate. +# Calls the shared promotion gate workflow. +# dev PRs: no gate (engineer self-merges). +# uat PRs: QA approval required. +# main PRs: UAT approval required (uat→main promotions). on: pull_request_review: types: [submitted, dismissed] pull_request: - branches: [main] + branches: [uat, main] types: [opened, reopened, synchronize] jobs: - dual-approval: + promotion-gate: uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main secrets: inherit with: pr_number: ${{ github.event.pull_request.number }} + -- 2.52.0