Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56d10a1d40 | |||
| 1f02811731 | |||
| 7b58f684cf | |||
| e2f220c418 | |||
| dff1265435 | |||
| 7c58826668 | |||
| 4edc829b3f | |||
| 8f10be39bd | |||
| 27212a91e1 | |||
| 7b72306133 | |||
| e16e6255d0 | |||
| 4beb0c4d0e | |||
| 175d3ec6a2 | |||
| e63cd03267 | |||
| 4d878c8737 | |||
| 5f817ec4f6 | |||
| 490807cef6 | |||
| 06d7dfb212 | |||
| ba508b8fc4 | |||
| b928fff4a5 | |||
| df6a5967ea | |||
| 415e32cdc9 | |||
| aa32e7a353 | |||
| 67bfe5ff5c | |||
| c08f3fbdbe | |||
| 02dc79b739 | |||
| d1097c2dbf | |||
| 5fa14ab353 | |||
| acd53c297b | |||
| fd66b119b3 | |||
| 21026cc992 | |||
| 95096562e4 | |||
| 62baf2bd5e | |||
| d2da09406a | |||
| a975192dfb | |||
| 2c80d0451e | |||
| d4a4e9a355 | |||
| a08c0fc368 | |||
| d0a6794576 | |||
| 00c270b0d4 | |||
| 7f115e0d6e | |||
| 9d02f504fd | |||
| 65c25067ec | |||
| 4c6324c4c2 | |||
| ca4832bcc3 | |||
| d6c8a8bbfc | |||
| 3d91572b59 | |||
| f0f3bd51a4 | |||
| 6e9c97593c | |||
| a5398e8409 | |||
| bb1df5f3f6 | |||
| 1bf5c2431c | |||
| 08a3009ba8 | |||
| b3f1f65b2f | |||
| 74a5bb0a01 | |||
| 9249f151a8 | |||
| dd782fbea0 | |||
| 0a52a8effa | |||
| 902f206e32 | |||
| 4344d33349 | |||
| 8ac890a1c6 | |||
| 6189f2b983 | |||
| 4296eb97fb | |||
| 87bf1a321f |
@@ -0,0 +1,20 @@
|
|||||||
|
name: Dual Approval (CTO + QA)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted, dismissed]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dual-approval:
|
||||||
|
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
pr_number: ${{ github.event.pull_request.number }}
|
||||||
+48
-100
@@ -7,13 +7,29 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# Only one E2E run at a time: the shared E2E_RELEASE (headlamp-e2e) in
|
||||||
|
# privilegedescalation-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:
|
env:
|
||||||
HEADLAMP_NAMESPACE: kube-system
|
E2E_NAMESPACE: privilegedescalation-dev
|
||||||
HEADLAMP_DEPLOY: headlamp
|
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:
|
jobs:
|
||||||
e2e:
|
e2e:
|
||||||
runs-on: local-ubuntu-latest
|
runs-on: runners-privilegedescalation
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -21,125 +37,57 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Setup kubectl
|
||||||
|
uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build plugin
|
- name: Build plugin
|
||||||
run: npm run build
|
run: npx @kinvolk/headlamp-plugin build
|
||||||
|
|
||||||
- name: Setup kubectl
|
- name: Deploy E2E Headlamp instance
|
||||||
uses: azure/setup-kubectl@v4
|
run: scripts/deploy-e2e-headlamp.sh
|
||||||
|
|
||||||
- name: Ensure PVC exists
|
- name: Load E2E environment
|
||||||
run: kubectl apply -f deployment/headlamp-plugins-pvc.yaml
|
|
||||||
|
|
||||||
- name: Patch Headlamp deployment with shared volume mount
|
|
||||||
run: |
|
run: |
|
||||||
NS="$HEADLAMP_NAMESPACE"
|
if [ -f .env.e2e ]; then
|
||||||
DEPLOY="$HEADLAMP_DEPLOY"
|
cat .env.e2e >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# Check if the plugins volume and mount already exist (by name or mountPath)
|
|
||||||
DEPLOY_JSON=$(kubectl get deploy "$DEPLOY" -n "$NS" -o json)
|
|
||||||
HAS_VOL=$(echo "$DEPLOY_JSON" | \
|
|
||||||
python3 -c "import sys,json; d=json.load(sys.stdin); vols=d['spec']['template']['spec'].get('volumes',[]); print('yes' if any(v.get('persistentVolumeClaim',{}).get('claimName')=='headlamp-plugins' or v.get('name')=='plugins' for v in vols) else '')")
|
|
||||||
HAS_MOUNT=$(echo "$DEPLOY_JSON" | \
|
|
||||||
python3 -c "import sys,json; d=json.load(sys.stdin); mounts=d['spec']['template']['spec']['containers'][0].get('volumeMounts',[]); print('yes' if any(m.get('mountPath')=='/headlamp/plugins' or m.get('name')=='plugins' for m in mounts) else '')")
|
|
||||||
|
|
||||||
NEEDS_PATCH=false
|
|
||||||
|
|
||||||
if [ -z "$HAS_VOL" ]; then
|
|
||||||
echo "Adding plugins PVC volume..."
|
|
||||||
kubectl patch deploy "$DEPLOY" -n "$NS" --type=json -p '[
|
|
||||||
{"op":"add","path":"/spec/template/spec/volumes/-","value":{
|
|
||||||
"name":"plugins",
|
|
||||||
"persistentVolumeClaim":{"claimName":"headlamp-plugins"}
|
|
||||||
}}
|
|
||||||
]'
|
|
||||||
NEEDS_PATCH=true
|
|
||||||
else
|
else
|
||||||
echo "Plugins volume already present, skipping."
|
echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$HAS_MOUNT" ]; then
|
|
||||||
echo "Adding plugins volume mount..."
|
|
||||||
kubectl patch deploy "$DEPLOY" -n "$NS" --type=json -p '[
|
|
||||||
{"op":"add","path":"/spec/template/spec/containers/0/volumeMounts/-","value":{
|
|
||||||
"name":"plugins",
|
|
||||||
"mountPath":"/headlamp/plugins",
|
|
||||||
"readOnly":true
|
|
||||||
}}
|
|
||||||
]'
|
|
||||||
NEEDS_PATCH=true
|
|
||||||
else
|
|
||||||
echo "Plugins volume mount already present, skipping."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set the plugins directory via env var
|
|
||||||
kubectl set env deploy/"$DEPLOY" -n "$NS" \
|
|
||||||
HEADLAMP_CONFIG_PLUGIN_DIR=/headlamp/plugins
|
|
||||||
|
|
||||||
# Wait for rollout
|
|
||||||
kubectl rollout status deploy/"$DEPLOY" -n "$NS" --timeout=120s
|
|
||||||
|
|
||||||
- name: Deploy plugin via shared volume
|
|
||||||
run: scripts/deploy-plugin-via-volume.sh
|
|
||||||
|
|
||||||
- name: Preflight — verify Headlamp and plugin availability
|
|
||||||
env:
|
|
||||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
|
||||||
run: |
|
|
||||||
PLUGIN_NAME=$(node -p "require('./package.json').name")
|
|
||||||
EXPECTED=$(node -p "require('./package.json').version")
|
|
||||||
echo "Expecting: $PLUGIN_NAME@$EXPECTED"
|
|
||||||
|
|
||||||
# Wait for Headlamp to be reachable
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 "$HEADLAMP_URL" || true)
|
|
||||||
if [ "$HTTP_CODE" != "000" ]; then
|
|
||||||
echo "Headlamp responded HTTP $HTTP_CODE"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting for Headlamp... ($i/30)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$HTTP_CODE" = "000" ]; then
|
|
||||||
echo "::error::Cannot reach Headlamp at $HEADLAMP_URL after 60s"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify plugin is visible
|
|
||||||
PLUGIN_JSON=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
|
|
||||||
node -e "
|
|
||||||
const plugins = JSON.parse(process.argv[1]);
|
|
||||||
console.log('Installed plugins:');
|
|
||||||
for (const p of plugins) console.log(' ' + p.name + '@' + (p.version||'unknown'));
|
|
||||||
const ours = plugins.find(p => p.name === '$PLUGIN_NAME' || p.name === 'polaris' || p.name.includes('polaris'));
|
|
||||||
if (!ours) {
|
|
||||||
console.log('::warning::Plugin $PLUGIN_NAME not yet visible — Headlamp may need a restart');
|
|
||||||
} else {
|
|
||||||
console.log('Found plugin: ' + ours.name + ' at path ' + ours.path);
|
|
||||||
}
|
|
||||||
" "$PLUGIN_JSON"
|
|
||||||
|
|
||||||
- name: Install Playwright browsers
|
- name: Install Playwright browsers
|
||||||
run: npx playwright install --with-deps chromium
|
run: npx playwright install --with-deps chromium
|
||||||
|
|
||||||
- name: Run E2E tests
|
- name: Run E2E tests
|
||||||
run: npm run e2e
|
run: npm run e2e
|
||||||
env:
|
env:
|
||||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
|
||||||
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }}
|
HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
|
||||||
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
|
|
||||||
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
|
- 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
|
- name: Upload Playwright report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
@@ -147,7 +95,7 @@ jobs:
|
|||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
|
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
|
||||||
|
secrets:
|
||||||
|
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||||
|
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||||
with:
|
with:
|
||||||
version: ${{ inputs.version }}
|
version: ${{ inputs.version }}
|
||||||
upstream-repo: 'FairwindsOps/polaris'
|
upstream-repo: 'FairwindsOps/polaris'
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ e2e/.auth/
|
|||||||
test-results/
|
test-results/
|
||||||
.playwright-mcp/
|
.playwright-mcp/
|
||||||
.env
|
.env
|
||||||
|
.env.e2e
|
||||||
.env.local
|
.env.local
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|||||||
+27
-1
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-03-22
|
||||||
|
|
||||||
|
First stable release. The plugin API (routes, sidebar entries, settings schema, and app bar action) is
|
||||||
|
now frozen — no breaking changes without a new major version.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Patched 8 of 9 npm audit vulnerabilities via `pnpm.overrides` (#92)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Dual-approval CI check**: PRs now require approval from both CTO and QA before merging (#98, #76)
|
||||||
|
- **ExemptionManager test suite**: Full coverage of annotation-based exemption flows, exemption creation, and inline feedback (#82)
|
||||||
|
- **RBAC preflight check**: `deploy-e2e-headlamp.sh` now verifies runner RBAC before attempting E2E deploy (#80)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **E2E infrastructure overhaul**: Replaced Dockerfile.e2e with ConfigMap volume mount for plugin loading; tests now run in the `privilegedescalation-dev` namespace (#73, #89, #94)
|
||||||
|
- **E2E token auth**: Workflow uses GitHub App token auth and handles the `/token` redirect correctly (#97)
|
||||||
|
- **E2E HTTP readiness**: `deploy-e2e-headlamp.sh` waits for HTTP reachability after rollout before running tests (#104)
|
||||||
|
- **E2E runner label**: Updated to `runners-privilegedescalation` for self-hosted ARC runners (#71)
|
||||||
|
- **Direct devDependencies**: Added `typescript`, `eslint`, `prettier`, and `@headlamp-k8s/eslint-config` as explicit direct devDependencies to prevent phantom-dep failures in clean installs (#95, #102)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **pnpm version pinned**: `packageManager` field in `package.json` pins the pnpm version used in CI (#103)
|
||||||
|
- **GitHub Actions SHA pinning**: Renovate `pinDigests` enabled to SHA-pin all GitHub Actions (#105)
|
||||||
|
- **ArtifactHub metadata polish**: Improved `install` instructions and `changes` section formatting (#82)
|
||||||
|
|
||||||
## [0.6.0] - 2026-03-04
|
## [0.6.0] - 2026-03-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -270,7 +295,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Automated release workflow
|
- Automated release workflow
|
||||||
- Basic CI/CD pipeline
|
- Basic CI/CD pipeline
|
||||||
|
|
||||||
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.6.0...HEAD
|
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v1.0.0...HEAD
|
||||||
|
[1.0.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.7.2...v1.0.0
|
||||||
[0.6.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.6.0
|
[0.6.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.6.0
|
||||||
[0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5
|
[0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5
|
||||||
[0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
|
[0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp # adjust to match your Headlamp service account
|
name: headlamp # adjust to match your Headlamp service account
|
||||||
namespace: kube-system # adjust to match the namespace Headlamp runs in
|
namespace: headlamp # adjust to match the namespace Headlamp runs in
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -197,7 +197,7 @@ npm test
|
|||||||
npm run test:watch
|
npm run test:watch
|
||||||
|
|
||||||
# E2E tests (Playwright)
|
# E2E tests (Playwright)
|
||||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||||
npm run e2e
|
npm run e2e
|
||||||
npm run e2e:headed # see browser
|
npm run e2e:headed # see browser
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
|
|||||||
+46
-4
@@ -1,4 +1,4 @@
|
|||||||
version: "0.7.1"
|
version: "1.0.0"
|
||||||
name: headlamp-polaris
|
name: headlamp-polaris
|
||||||
displayName: Polaris
|
displayName: Polaris
|
||||||
createdAt: "2026-02-05T19:00:00Z"
|
createdAt: "2026-02-05T19:00:00Z"
|
||||||
@@ -11,6 +11,7 @@ description: >-
|
|||||||
`polaris-dashboard` service in the `polaris` namespace.
|
`polaris-dashboard` service in the `polaris` namespace.
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||||
|
appVersion: "10.1.6"
|
||||||
category: security
|
category: security
|
||||||
keywords:
|
keywords:
|
||||||
- polaris
|
- polaris
|
||||||
@@ -24,11 +25,52 @@ links:
|
|||||||
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||||
- name: Polaris
|
- name: Polaris
|
||||||
url: "https://polaris.docs.fairwinds.com/"
|
url: "https://polaris.docs.fairwinds.com/"
|
||||||
|
install: |
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. [Headlamp](https://headlamp.dev) v0.26.0 or later
|
||||||
|
2. [Fairwinds Polaris](https://polaris.docs.fairwinds.com/) installed and the dashboard running in your cluster
|
||||||
|
|
||||||
|
### Install via Headlamp Plugin Catalog
|
||||||
|
|
||||||
|
1. Open Headlamp and navigate to **Settings → Plugin Catalog**
|
||||||
|
2. Search for **"Polaris"**
|
||||||
|
3. Click **Install** and restart Headlamp when prompted
|
||||||
|
|
||||||
|
The plugin is sourced directly from [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp/headlamp-polaris).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After installation, the Polaris plugin adds:
|
||||||
|
- A **cluster score badge** in the Headlamp app bar
|
||||||
|
- A **Polaris** section in the sidebar with the full dashboard and namespace drill-downs
|
||||||
|
- An **inline audit panel** on Deployment, StatefulSet, DaemonSet, Job, and CronJob detail pages
|
||||||
|
|
||||||
|
For more information, see the [README](https://github.com/privilegedescalation/headlamp-polaris-plugin/blob/main/README.md).
|
||||||
|
changes:
|
||||||
|
- kind: security
|
||||||
|
description: Patched 8 npm audit vulnerabilities via pnpm.overrides
|
||||||
|
- kind: added
|
||||||
|
description: Dual-approval required CI check — PRs must be approved by both CTO and QA before merge
|
||||||
|
- kind: added
|
||||||
|
description: ExemptionManager test suite — full coverage of annotation-based exemption flows
|
||||||
|
- kind: fixed
|
||||||
|
description: E2E infrastructure overhauled — ConfigMap volume mount replaces Dockerfile-based approach, tests run in privilegedescalation-dev namespace
|
||||||
|
- kind: fixed
|
||||||
|
description: E2E workflow uses token auth and waits for HTTP reachability before running tests
|
||||||
|
- kind: fixed
|
||||||
|
description: Added explicit direct devDependencies (typescript, eslint, prettier, @headlamp-k8s/eslint-config) to prevent phantom dep failures
|
||||||
|
- kind: changed
|
||||||
|
description: pnpm version pinned via packageManager field; GitHub Actions SHA-pinned via Renovate pinDigests
|
||||||
|
- kind: changed
|
||||||
|
description: v1.0.0 stable release — plugin API (routes, sidebar, settings schema, app bar action) is stable and will not change without a major version bump
|
||||||
maintainers:
|
maintainers:
|
||||||
- name: privilegedescalation
|
- name: privilegedescalation
|
||||||
email: "chris@farhood.org"
|
email: "chris@farhood.org"
|
||||||
annotations:
|
annotations:
|
||||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.1/headlamp-polaris-0.7.1.tar.gz"
|
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v1.0.0/headlamp-polaris-1.0.0.tar.gz"
|
||||||
headlamp/plugin/version-compat: ">=0.26"
|
headlamp/plugin/version-compat: ">=0.26"
|
||||||
headlamp/plugin/archive-checksum: sha256:5ea27f3beeab9730a13a752a0a7c2270eca83dcbbe813a6cabeb6a22fa9d5174
|
headlamp/plugin/archive-checksum: sha256:a165e871b40f11a44950aa9f10eb7f7883276f749026ae7a4f886278ecd9bd7d
|
||||||
headlamp/plugin/distro-compat: in-cluster
|
headlamp/plugin/distro-compat: "in-cluster,web,desktop"
|
||||||
|
|||||||
@@ -1,59 +1,12 @@
|
|||||||
---
|
---
|
||||||
# RBAC for the GitHub Actions CI runner to perform E2E test setup.
|
# RBAC for the GitHub Actions CI runner to manage E2E Headlamp instances.
|
||||||
# CI-only test fixture — NOT for production use.
|
# CI-only test fixture — NOT for production use.
|
||||||
#
|
#
|
||||||
# Grants the ARC runner service account namespace-scoped permissions in
|
# This file is a REFERENCE ONLY. The canonical manifest lives in:
|
||||||
# kube-system to patch the Headlamp deployment (add shared volume mount),
|
# privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml
|
||||||
# manage PVCs, run temporary pods, and restart deployments.
|
|
||||||
#
|
#
|
||||||
# No cluster-scoped permissions needed — the E2E workflow uses kubectl patch
|
# The infra repo is managed by Flux GitOps and is the source of truth.
|
||||||
# instead of helm upgrade, avoiding the need to read ClusterRole/ClusterRoleBinding.
|
# Do not apply this file directly — it is kept here for developer reference only.
|
||||||
#
|
#
|
||||||
# Apply with: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
# E2E resources run in `privilegedescalation-dev` — nothing persists beyond a test run.
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
# RBAC is managed via Flux from privilegedescalation/infra — do not apply manually.
|
||||||
kind: Role
|
|
||||||
metadata:
|
|
||||||
name: e2e-ci-runner
|
|
||||||
namespace: kube-system
|
|
||||||
rules:
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["persistentvolumeclaims"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["pods"]
|
|
||||||
verbs: ["get", "list", "create", "delete", "watch"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["pods/attach"]
|
|
||||||
verbs: ["create", "get"]
|
|
||||||
- apiGroups: ["apps"]
|
|
||||||
resources: ["deployments"]
|
|
||||||
verbs: ["get", "list", "patch", "watch"]
|
|
||||||
- apiGroups: ["apps"]
|
|
||||||
resources: ["deployments/scale"]
|
|
||||||
verbs: ["patch"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["secrets"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["configmaps"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["services"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["serviceaccounts"]
|
|
||||||
verbs: ["get", "list"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: RoleBinding
|
|
||||||
metadata:
|
|
||||||
name: e2e-ci-runner-binding
|
|
||||||
namespace: kube-system
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: local-ubuntu-latest-gha-rs-no-permission
|
|
||||||
namespace: arc-runners
|
|
||||||
roleRef:
|
|
||||||
kind: Role
|
|
||||||
name: e2e-ci-runner
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
# Headlamp Helm values for E2E testing with shared volume plugin deployment.
|
|
||||||
#
|
|
||||||
# The CI runner and Headlamp pod share a PVC so that the runner can copy
|
|
||||||
# built plugin artifacts directly into Headlamp's plugins directory.
|
|
||||||
# This is a CI-only mechanism — production plugin distribution uses ArtifactHub.
|
|
||||||
|
|
||||||
# Point Headlamp at the shared plugins mount
|
|
||||||
config:
|
|
||||||
pluginsDir: /headlamp/plugins
|
|
||||||
|
|
||||||
# PVC-backed volume shared with the CI runner
|
|
||||||
volumes:
|
|
||||||
- name: plugins
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: headlamp-plugins
|
|
||||||
|
|
||||||
# Mount into the Headlamp container
|
|
||||||
volumeMounts:
|
|
||||||
- name: plugins
|
|
||||||
mountPath: /headlamp/plugins
|
|
||||||
readOnly: true
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
# PVC for sharing built plugin artifacts between the CI runner and Headlamp.
|
|
||||||
# Used only in E2E test environments — not for production.
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: headlamp-plugins
|
|
||||||
namespace: kube-system
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 128Mi
|
|
||||||
+2
-2
@@ -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
|
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||||
|
|
||||||
# Verify Headlamp is deployed
|
# Verify Headlamp is deployed
|
||||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation Methods
|
## Installation Methods
|
||||||
@@ -59,7 +59,7 @@ kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -268,10 +268,9 @@ npm run e2e
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create token
|
# Create token
|
||||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||||
|
|
||||||
# Port-forward for local testing
|
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||||
|
|||||||
+16
-16
@@ -33,7 +33,7 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View Headlamp pod logs (plugin sidecar)
|
# View Headlamp pod logs (plugin sidecar)
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
# 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**:
|
**Verify plugin files exist**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
kubectl exec -n headlamp deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||||
# Should show: headlamp-polaris-plugin/
|
# Should show: headlamp-polaris-plugin/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ Expected subjects:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
For OIDC mode:
|
For OIDC mode:
|
||||||
@@ -154,7 +154,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -169,7 +169,7 @@ Service account mode:
|
|||||||
```bash
|
```bash
|
||||||
# Impersonate Headlamp service account
|
# Impersonate Headlamp service account
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
--resource-name=polaris-dashboard \
|
--resource-name=polaris-dashboard \
|
||||||
-n polaris
|
-n polaris
|
||||||
# Expected: yes
|
# Expected: yes
|
||||||
@@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \
|
|||||||
After applying RBAC changes:
|
After applying RBAC changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl rollout restart deployment headlamp -n kube-system
|
kubectl rollout restart deployment headlamp -n headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -490,7 +490,7 @@ Run this script to test all RBAC components:
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
NS="polaris"
|
NS="polaris"
|
||||||
SA="headlamp"
|
SA="headlamp"
|
||||||
SA_NS="kube-system"
|
SA_NS="headlamp"
|
||||||
|
|
||||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||||
|
|
||||||
@@ -529,8 +529,8 @@ echo "=== Test complete ==="
|
|||||||
Test connectivity from Headlamp to Polaris:
|
Test connectivity from Headlamp to Polaris:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create debug pod in kube-system namespace
|
# Create debug pod in headlamp namespace
|
||||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
kubectl run netdebug -n headlamp --rm -it --image=nicolaka/netshoot -- bash
|
||||||
|
|
||||||
# Inside pod, test DNS and HTTP
|
# Inside pod, test DNS and HTTP
|
||||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||||
@@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View recent audit logs (location varies by cluster)
|
# View recent audit logs (location varies by cluster)
|
||||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||||
|
|
||||||
# Look for lines with:
|
# Look for lines with:
|
||||||
# "reason": "Forbidden"
|
# "reason": "Forbidden"
|
||||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
# "user": "system:serviceaccount:headlamp:headlamp"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -567,7 +567,7 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
|||||||
**Check sidecar logs**:
|
**Check sidecar logs**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
**Common errors**:
|
**Common errors**:
|
||||||
@@ -591,7 +591,7 @@ Error: 404 Not Found
|
|||||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
kubectl get configmap headlamp-plugin-config -n headlamp -o yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected format:
|
Expected format:
|
||||||
@@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue:
|
|||||||
1. **Version Information**:
|
1. **Version Information**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
kubectl get pods -n headlamp -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Plugin Version**:
|
2. **Plugin Version**:
|
||||||
|
|
||||||
- Check Settings → Plugins in Headlamp UI
|
- 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 headlamp deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||||
|
|
||||||
3. **Browser Console Output**:
|
3. **Browser Console Output**:
|
||||||
|
|
||||||
@@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue:
|
|||||||
5. **Pod Logs**:
|
5. **Pod Logs**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
kubectl logs -n headlamp deployment/headlamp -c headlamp --tail=100
|
||||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+20
-20
@@ -41,11 +41,11 @@ pluginsManager:
|
|||||||
```bash
|
```bash
|
||||||
# Install Headlamp
|
# Install Headlamp
|
||||||
helm install headlamp headlamp/headlamp \
|
helm install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
|
|
||||||
# Wait for deployment
|
# Wait for deployment
|
||||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
kubectl -n headlamp wait --for=condition=available deployment/headlamp --timeout=300s
|
||||||
```
|
```
|
||||||
|
|
||||||
After installation, install the plugin via Headlamp UI (**Settings → Plugins → Catalog**).
|
After installation, install the plugin via Headlamp UI (**Settings → Plugins → Catalog**).
|
||||||
@@ -131,7 +131,7 @@ Deploy:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml \
|
--values headlamp-values.yaml \
|
||||||
--wait \
|
--wait \
|
||||||
--timeout 5m
|
--timeout 5m
|
||||||
@@ -177,7 +177,7 @@ apiVersion: v1
|
|||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-plugin-config
|
name: headlamp-plugin-config
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
data:
|
data:
|
||||||
plugin.yml: |
|
plugin.yml: |
|
||||||
- name: headlamp-polaris-plugin
|
- name: headlamp-polaris-plugin
|
||||||
@@ -191,7 +191,7 @@ Apply ConfigMap then deploy Headlamp:
|
|||||||
kubectl apply -f headlamp-plugin-config.yaml
|
kubectl apply -f headlamp-plugin-config.yaml
|
||||||
|
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2
|
|||||||
kind: HelmRelease
|
kind: HelmRelease
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
spec:
|
spec:
|
||||||
interval: 30m
|
interval: 30m
|
||||||
chart:
|
chart:
|
||||||
@@ -300,7 +300,7 @@ kubectl apply -f helmrepository.yaml
|
|||||||
kubectl apply -f helmrelease.yaml
|
kubectl apply -f helmrelease.yaml
|
||||||
|
|
||||||
# Watch deployment
|
# Watch deployment
|
||||||
flux get helmreleases -n kube-system --watch
|
flux get helmreleases -n headlamp --watch
|
||||||
```
|
```
|
||||||
|
|
||||||
## RBAC Configuration
|
## RBAC Configuration
|
||||||
@@ -329,7 +329,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -349,7 +349,7 @@ helm repo update
|
|||||||
|
|
||||||
# Upgrade Headlamp (preserves plugin configuration)
|
# Upgrade Headlamp (preserves plugin configuration)
|
||||||
helm upgrade headlamp headlamp/headlamp \
|
helm upgrade headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml \
|
--values headlamp-values.yaml \
|
||||||
--wait
|
--wait
|
||||||
```
|
```
|
||||||
@@ -365,15 +365,15 @@ helm upgrade headlamp headlamp/headlamp \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Update ConfigMap with new version
|
# Update ConfigMap with new version
|
||||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
kubectl -n headlamp edit configmap headlamp-plugin-config
|
||||||
|
|
||||||
# Update version and URL:
|
# Update version and URL:
|
||||||
# version: 0.3.6
|
# version: 0.3.6
|
||||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||||
|
|
||||||
# Restart deployment to trigger init container
|
# Restart deployment to trigger init container
|
||||||
kubectl -n kube-system rollout restart deployment/headlamp
|
kubectl -n headlamp rollout restart deployment/headlamp
|
||||||
kubectl -n kube-system rollout status deployment/headlamp
|
kubectl -n headlamp rollout status deployment/headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
@@ -382,25 +382,25 @@ kubectl -n kube-system rollout status deployment/headlamp
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check Headlamp values
|
# Check Headlamp values
|
||||||
helm get values headlamp -n kube-system
|
helm get values headlamp -n headlamp
|
||||||
|
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# If missing, reinstall plugin via UI or check init container logs
|
# If missing, reinstall plugin via UI or check init container logs
|
||||||
kubectl -n kube-system logs deployment/headlamp -c install-polaris-plugin
|
kubectl -n headlamp logs deployment/headlamp -c install-polaris-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
### Helm Release Stuck
|
### Helm Release Stuck
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check Helm release status
|
# Check Helm release status
|
||||||
helm list -n kube-system
|
helm list -n headlamp
|
||||||
|
|
||||||
# If stuck, force upgrade
|
# If stuck, force upgrade
|
||||||
helm upgrade headlamp headlamp/headlamp \
|
helm upgrade headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml \
|
--values headlamp-values.yaml \
|
||||||
--force \
|
--force \
|
||||||
--wait
|
--wait
|
||||||
@@ -410,13 +410,13 @@ helm upgrade headlamp headlamp/headlamp \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check HelmRelease status
|
# Check HelmRelease status
|
||||||
flux get helmreleases -n kube-system
|
flux get helmreleases -n headlamp
|
||||||
|
|
||||||
# Check events
|
# Check events
|
||||||
kubectl -n kube-system describe helmrelease headlamp
|
kubectl -n headlamp describe helmrelease headlamp
|
||||||
|
|
||||||
# Force reconciliation
|
# Force reconciliation
|
||||||
flux reconcile helmrelease headlamp -n kube-system
|
flux reconcile helmrelease headlamp -n headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -71,7 +71,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
|||||||
|
|
||||||
# Test permission
|
# Test permission
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ apiVersion: v1
|
|||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-plugin-config
|
name: headlamp-plugin-config
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: headlamp
|
app.kubernetes.io/name: headlamp
|
||||||
app.kubernetes.io/component: plugin-config
|
app.kubernetes.io/component: plugin-config
|
||||||
@@ -109,7 +109,7 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: headlamp
|
app.kubernetes.io/name: headlamp
|
||||||
spec:
|
spec:
|
||||||
@@ -194,7 +194,7 @@ apiVersion: v1
|
|||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: headlamp
|
app.kubernetes.io/name: headlamp
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: headlamp
|
app.kubernetes.io/name: headlamp
|
||||||
spec:
|
spec:
|
||||||
@@ -235,27 +235,27 @@ kubectl apply -f headlamp-service.yaml
|
|||||||
kubectl apply -f headlamp-serviceaccount.yaml
|
kubectl apply -f headlamp-serviceaccount.yaml
|
||||||
|
|
||||||
# Wait for deployment to be ready
|
# Wait for deployment to be ready
|
||||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
kubectl -n headlamp wait --for=condition=available deployment/headlamp --timeout=300s
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Verify Deployment
|
### 2. Verify Deployment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check pods are running
|
# Check pods are running
|
||||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# NAME READY STATUS RESTARTS AGE
|
# NAME READY STATUS RESTARTS AGE
|
||||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
|
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
|
||||||
|
|
||||||
# Check init container logs
|
# Check init container logs
|
||||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# Plugin installation complete
|
# Plugin installation complete
|
||||||
|
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
@@ -273,7 +273,7 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Port-forward to access locally
|
# Port-forward to access locally
|
||||||
kubectl -n kube-system port-forward service/headlamp 8080:80
|
kubectl -n headlamp port-forward service/headlamp 8080:80
|
||||||
|
|
||||||
# Open browser to http://localhost:8080
|
# Open browser to http://localhost:8080
|
||||||
```
|
```
|
||||||
@@ -309,7 +309,7 @@ k8s/
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
|
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
|
|
||||||
commonLabels:
|
commonLabels:
|
||||||
app.kubernetes.io/name: headlamp
|
app.kubernetes.io/name: headlamp
|
||||||
@@ -401,7 +401,7 @@ spec:
|
|||||||
- apiVersion: apps/v1
|
- apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrading the Plugin
|
## Upgrading the Plugin
|
||||||
@@ -410,24 +410,24 @@ spec:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Edit ConfigMap with new version
|
# Edit ConfigMap with new version
|
||||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
kubectl -n headlamp edit configmap headlamp-plugin-config
|
||||||
|
|
||||||
# Update version and URL:
|
# Update version and URL:
|
||||||
# version: 0.3.6
|
# version: 0.3.6
|
||||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||||
|
|
||||||
# Restart deployment to trigger init container
|
# Restart deployment to trigger init container
|
||||||
kubectl -n kube-system rollout restart deployment/headlamp
|
kubectl -n headlamp rollout restart deployment/headlamp
|
||||||
|
|
||||||
# Wait for rollout to complete
|
# Wait for rollout to complete
|
||||||
kubectl -n kube-system rollout status deployment/headlamp
|
kubectl -n headlamp rollout status deployment/headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verify Upgrade
|
### Verify Upgrade
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check init container logs
|
# Check init container logs
|
||||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||||
|
|
||||||
# Verify new version in UI
|
# Verify new version in UI
|
||||||
# Navigate to Settings → Plugins in Headlamp
|
# Navigate to Settings → Plugins in Headlamp
|
||||||
@@ -439,7 +439,7 @@ kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check init container logs
|
# Check init container logs
|
||||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||||
|
|
||||||
# Common issues:
|
# Common issues:
|
||||||
# 1. Network connectivity to GitHub
|
# 1. Network connectivity to GitHub
|
||||||
@@ -451,14 +451,14 @@ kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify HEADLAMP_CONFIG_WATCH_PLUGINS is false
|
# Verify HEADLAMP_CONFIG_WATCH_PLUGINS is false
|
||||||
kubectl -n kube-system get deployment headlamp -o yaml | grep WATCH_PLUGINS
|
kubectl -n headlamp get deployment headlamp -o yaml | grep WATCH_PLUGINS
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# - name: HEADLAMP_CONFIG_WATCH_PLUGINS
|
# - name: HEADLAMP_CONFIG_WATCH_PLUGINS
|
||||||
# value: "false"
|
# value: "false"
|
||||||
|
|
||||||
# If not set or "true", update deployment
|
# If not set or "true", update deployment
|
||||||
kubectl -n kube-system edit deployment headlamp
|
kubectl -n headlamp edit deployment headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
### RBAC Permissions Denied
|
### RBAC Permissions Denied
|
||||||
@@ -466,7 +466,7 @@ kubectl -n kube-system edit deployment headlamp
|
|||||||
```bash
|
```bash
|
||||||
# Test RBAC
|
# Test RBAC
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
|
||||||
|
|
||||||
# Verify Headlamp
|
# Verify Headlamp
|
||||||
kubectl -n kube-system get deployment headlamp
|
kubectl -n headlamp get deployment headlamp
|
||||||
kubectl -n kube-system get svc headlamp
|
kubectl -n headlamp get svc headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Production Checklist
|
## Production Checklist
|
||||||
@@ -60,17 +60,17 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
|||||||
|
|
||||||
# 2. Verify RBAC permissions
|
# 2. Verify RBAC permissions
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
# Expected: yes
|
# Expected: yes
|
||||||
|
|
||||||
# 3. Check Headlamp logs for plugin loading
|
# 3. Check Headlamp logs for plugin loading
|
||||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||||
# Expected: No errors related to plugin loading
|
# Expected: No errors related to plugin loading
|
||||||
|
|
||||||
# 4. Verify plugin files exist
|
# 4. Verify plugin files exist
|
||||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
kubectl -n headlamp exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
# Expected: dist/, package.json present
|
# Expected: dist/, package.json present
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ spec:
|
|||||||
- from:
|
- from:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: headlamp
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
component: kube-apiserver
|
component: kube-apiserver
|
||||||
@@ -241,7 +241,7 @@ apiVersion: policy/v1
|
|||||||
kind: PodDisruptionBudget
|
kind: PodDisruptionBudget
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-pdb
|
name: headlamp-pdb
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
spec:
|
spec:
|
||||||
minAvailable: 1
|
minAvailable: 1
|
||||||
selector:
|
selector:
|
||||||
@@ -295,7 +295,7 @@ apiVersion: monitoring.coreos.com/v1
|
|||||||
kind: ServiceMonitor
|
kind: ServiceMonitor
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
@@ -312,10 +312,10 @@ spec:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View logs
|
# View logs
|
||||||
kubectl -n kube-system logs deployment/headlamp -f
|
kubectl -n headlamp logs deployment/headlamp -f
|
||||||
|
|
||||||
# Filter for plugin-related logs
|
# Filter for plugin-related logs
|
||||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||||
```
|
```
|
||||||
|
|
||||||
**Polaris Dashboard Logs:**
|
**Polaris Dashboard Logs:**
|
||||||
@@ -341,14 +341,14 @@ apiVersion: monitoring.coreos.com/v1
|
|||||||
kind: PrometheusRule
|
kind: PrometheusRule
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-alerts
|
name: headlamp-alerts
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
spec:
|
spec:
|
||||||
groups:
|
groups:
|
||||||
- name: headlamp
|
- name: headlamp
|
||||||
interval: 30s
|
interval: 30s
|
||||||
rules:
|
rules:
|
||||||
- alert: HeadlampPodNotReady
|
- alert: HeadlampPodNotReady
|
||||||
expr: kube_pod_status_ready{namespace="kube-system", pod=~"headlamp-.*"} == 0
|
expr: kube_pod_status_ready{namespace="headlamp", pod=~"headlamp-.*"} == 0
|
||||||
for: 5m
|
for: 5m
|
||||||
labels:
|
labels:
|
||||||
severity: warning
|
severity: warning
|
||||||
@@ -422,9 +422,9 @@ If Headlamp or plugin becomes unavailable:
|
|||||||
2. **Redeploy Headlamp:**
|
2. **Redeploy Headlamp:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Reapply RBAC:**
|
3. **Reapply RBAC:**
|
||||||
@@ -436,7 +436,7 @@ If Headlamp or plugin becomes unavailable:
|
|||||||
4. **Verify plugin files:**
|
4. **Verify plugin files:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl -n kube-system exec deployment/headlamp -- \
|
kubectl -n headlamp exec deployment/headlamp -- \
|
||||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -268,10 +268,9 @@ npm run e2e
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create token
|
# Create token
|
||||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||||
|
|
||||||
# Port-forward for local testing
|
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Deploy or update Headlamp:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ apiVersion: v1
|
|||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: headlamp-plugin-config
|
name: headlamp-plugin-config
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
data:
|
data:
|
||||||
plugin.yml: |
|
plugin.yml: |
|
||||||
- name: headlamp-polaris-plugin
|
- name: headlamp-polaris-plugin
|
||||||
@@ -138,14 +138,14 @@ kubectl apply -f headlamp-plugin-config.yaml
|
|||||||
|
|
||||||
# Deploy/update Headlamp with sidecar
|
# Deploy/update Headlamp with sidecar
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
|
|
||||||
# Wait for pod to be ready
|
# 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 headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||||
|
|
||||||
# Verify plugin files
|
# Verify plugin files
|
||||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# drwxr-xr-x dist/
|
# drwxr-xr-x dist/
|
||||||
@@ -270,7 +270,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -284,10 +284,10 @@ See [RBAC Permissions](../user-guide/rbac-permissions.md) for detailed RBAC conf
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If you updated Helm values or ConfigMaps
|
# If you updated Helm values or ConfigMaps
|
||||||
kubectl -n kube-system rollout restart deployment/headlamp
|
kubectl -n headlamp rollout restart deployment/headlamp
|
||||||
|
|
||||||
# Wait for pod to be ready
|
# 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 headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Clear Browser Cache
|
### 3. Clear Browser Cache
|
||||||
@@ -312,14 +312,14 @@ kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# drwxr-xr-x dist/
|
# drwxr-xr-x dist/
|
||||||
# -rw-r--r-- package.json
|
# -rw-r--r-- package.json
|
||||||
|
|
||||||
# Check Headlamp logs for errors
|
# Check Headlamp logs for errors
|
||||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||||
|
|
||||||
# Expected: No errors related to plugin loading
|
# Expected: No errors related to plugin loading
|
||||||
|
|
||||||
@@ -345,13 +345,13 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Verify plugin files exist
|
# 1. Verify plugin files exist
|
||||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# Expected: dist/, package.json present
|
# Expected: dist/, package.json present
|
||||||
|
|
||||||
# 2. Check Headlamp logs for plugin errors
|
# 2. Check Headlamp logs for plugin errors
|
||||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||||
|
|
||||||
# 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
|
# 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ helm install polaris fairwinds-stable/polaris \
|
|||||||
```bash
|
```bash
|
||||||
# Wait 30 minutes for ArtifactHub sync
|
# Wait 30 minutes for ArtifactHub sync
|
||||||
# Or manually force Headlamp restart:
|
# Or manually force Headlamp restart:
|
||||||
kubectl -n kube-system rollout restart deployment/headlamp
|
kubectl -n headlamp rollout restart deployment/headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|||||||
@@ -67,14 +67,14 @@ kubectl -n polaris wait --for=condition=ready pod -l app.kubernetes.io/name=pola
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check Headlamp is deployed
|
# Check Headlamp is deployed
|
||||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# NAME READY STATUS RESTARTS AGE
|
# NAME READY STATUS RESTARTS AGE
|
||||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 1h
|
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 1h
|
||||||
|
|
||||||
# Check Headlamp version (must be v0.26+)
|
# Check Headlamp version (must be v0.26+)
|
||||||
kubectl -n kube-system get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}'
|
kubectl -n headlamp get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# ghcr.io/headlamp-k8s/headlamp:v0.39.0 (or similar)
|
# ghcr.io/headlamp-k8s/headlamp:v0.39.0 (or similar)
|
||||||
@@ -89,12 +89,12 @@ helm repo update
|
|||||||
|
|
||||||
# Install Headlamp
|
# Install Headlamp
|
||||||
helm install headlamp headlamp/headlamp \
|
helm install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--set config.pluginsDir="/headlamp/plugins" \
|
--set config.pluginsDir="/headlamp/plugins" \
|
||||||
--set pluginsManager.enabled=true
|
--set pluginsManager.enabled=true
|
||||||
|
|
||||||
# Wait for pod to be ready
|
# 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 headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||||
```
|
```
|
||||||
|
|
||||||
## RBAC Requirements
|
## RBAC Requirements
|
||||||
@@ -112,7 +112,7 @@ The plugin requires permissions to access the Polaris dashboard via Kubernetes s
|
|||||||
```bash
|
```bash
|
||||||
# Test if Headlamp service account has permission
|
# Test if Headlamp service account has permission
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ EOF
|
|||||||
|
|
||||||
# Update Headlamp
|
# Update Headlamp
|
||||||
helm upgrade --install headlamp headlamp/headlamp \
|
helm upgrade --install headlamp headlamp/headlamp \
|
||||||
--namespace kube-system \
|
--namespace headlamp \
|
||||||
--values headlamp-values.yaml
|
--values headlamp-values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -111,7 +111,7 @@ EOF
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- \
|
||||||
ls /headlamp/plugins/headlamp-polaris-plugin/dist/
|
ls /headlamp/plugins/headlamp-polaris-plugin/dist/
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
@@ -119,7 +119,7 @@ kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
|||||||
|
|
||||||
# Verify RBAC is correct
|
# Verify RBAC is correct
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ Cluster score badge in top navigation:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- \
|
||||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# If missing, reinstall via Headlamp UI or sidecar method
|
# If missing, reinstall via Headlamp UI or sidecar method
|
||||||
|
|||||||
@@ -38,17 +38,17 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
|||||||
|
|
||||||
# 3. Verify RBAC permissions
|
# 3. Verify RBAC permissions
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
# Expected output: yes
|
# Expected output: yes
|
||||||
|
|
||||||
# 4. Check Headlamp pod is running
|
# 4. Check Headlamp pod is running
|
||||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||||
|
|
||||||
# 5. Check Headlamp logs for plugin errors
|
# 5. Check Headlamp logs for plugin errors
|
||||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||||
|
|
||||||
# Expected: No errors
|
# Expected: No errors
|
||||||
```
|
```
|
||||||
@@ -57,7 +57,7 @@ kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify plugin files exist
|
# Verify plugin files exist
|
||||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
@@ -76,7 +76,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
|||||||
|
|
||||||
# Test permission (service account mode)
|
# Test permission (service account mode)
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View Headlamp pod logs (plugin sidecar)
|
# View Headlamp pod logs (plugin sidecar)
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||||
|
|
||||||
# Expected output:
|
# Expected output:
|
||||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
# 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**:
|
**Verify plugin files exist**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
kubectl exec -n headlamp deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||||
# Should show: headlamp-polaris-plugin/
|
# Should show: headlamp-polaris-plugin/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ Expected subjects:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
For OIDC mode:
|
For OIDC mode:
|
||||||
@@ -154,7 +154,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -169,7 +169,7 @@ Service account mode:
|
|||||||
```bash
|
```bash
|
||||||
# Impersonate Headlamp service account
|
# Impersonate Headlamp service account
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
--resource-name=polaris-dashboard \
|
--resource-name=polaris-dashboard \
|
||||||
-n polaris
|
-n polaris
|
||||||
# Expected: yes
|
# Expected: yes
|
||||||
@@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \
|
|||||||
After applying RBAC changes:
|
After applying RBAC changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl rollout restart deployment headlamp -n kube-system
|
kubectl rollout restart deployment headlamp -n headlamp
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -490,7 +490,7 @@ Run this script to test all RBAC components:
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
NS="polaris"
|
NS="polaris"
|
||||||
SA="headlamp"
|
SA="headlamp"
|
||||||
SA_NS="kube-system"
|
SA_NS="headlamp"
|
||||||
|
|
||||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||||
|
|
||||||
@@ -529,8 +529,8 @@ echo "=== Test complete ==="
|
|||||||
Test connectivity from Headlamp to Polaris:
|
Test connectivity from Headlamp to Polaris:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create debug pod in kube-system namespace
|
# Create debug pod in headlamp namespace
|
||||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
kubectl run netdebug -n headlamp --rm -it --image=nicolaka/netshoot -- bash
|
||||||
|
|
||||||
# Inside pod, test DNS and HTTP
|
# Inside pod, test DNS and HTTP
|
||||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||||
@@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View recent audit logs (location varies by cluster)
|
# View recent audit logs (location varies by cluster)
|
||||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||||
|
|
||||||
# Look for lines with:
|
# Look for lines with:
|
||||||
# "reason": "Forbidden"
|
# "reason": "Forbidden"
|
||||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
# "user": "system:serviceaccount:headlamp:headlamp"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -567,7 +567,7 @@ kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
|||||||
**Check sidecar logs**:
|
**Check sidecar logs**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
**Common errors**:
|
**Common errors**:
|
||||||
@@ -591,7 +591,7 @@ Error: 404 Not Found
|
|||||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
kubectl get configmap headlamp-plugin-config -n headlamp -o yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected format:
|
Expected format:
|
||||||
@@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue:
|
|||||||
1. **Version Information**:
|
1. **Version Information**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
kubectl get pods -n headlamp -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Plugin Version**:
|
2. **Plugin Version**:
|
||||||
|
|
||||||
- Check Settings → Plugins in Headlamp UI
|
- 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 headlamp deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||||
|
|
||||||
3. **Browser Console Output**:
|
3. **Browser Console Output**:
|
||||||
|
|
||||||
@@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue:
|
|||||||
5. **Pod Logs**:
|
5. **Pod Logs**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
kubectl logs -n headlamp deployment/headlamp -c headlamp --tail=100
|
||||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ spec:
|
|||||||
- from:
|
- from:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: headlamp
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
component: kube-apiserver
|
component: kube-apiserver
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -83,7 +83,7 @@ roleRef:
|
|||||||
```bash
|
```bash
|
||||||
# Test service account (in-cluster mode)
|
# Test service account (in-cluster mode)
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
|||||||
|
|
||||||
# Test permission
|
# Test permission
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp # Adjust to your Headlamp SA name
|
name: headlamp # Adjust to your Headlamp SA name
|
||||||
namespace: kube-system # Adjust to Headlamp's namespace
|
namespace: headlamp # Adjust to Headlamp's namespace
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -75,7 +75,7 @@ roleRef:
|
|||||||
**Adjust for your environment:**
|
**Adjust for your environment:**
|
||||||
|
|
||||||
- `subjects[0].name` - Your Headlamp service account name (often `headlamp`)
|
- `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 runs (often `headlamp`)
|
||||||
|
|
||||||
### Step 3: Apply and Verify
|
### Step 3: Apply and Verify
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
|||||||
|
|
||||||
# Test permission
|
# Test permission
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--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:
|
With service account mode:
|
||||||
|
|
||||||
- Single RoleBinding grants access to all Headlamp users
|
- 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:headlamp`
|
||||||
|
|
||||||
With token-auth mode:
|
With token-auth mode:
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -281,7 +281,7 @@ metadata:
|
|||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: headlamp
|
name: headlamp
|
||||||
namespace: kube-system
|
namespace: headlamp
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: Role
|
kind: Role
|
||||||
name: polaris-proxy-reader
|
name: polaris-proxy-reader
|
||||||
@@ -318,7 +318,7 @@ spec:
|
|||||||
- from:
|
- from:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: headlamp
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
component: kube-apiserver
|
component: kube-apiserver
|
||||||
@@ -411,7 +411,7 @@ Every plugin data fetch creates a Kubernetes API audit log entry.
|
|||||||
"level": "Metadata",
|
"level": "Metadata",
|
||||||
"verb": "get",
|
"verb": "get",
|
||||||
"user": {
|
"user": {
|
||||||
"username": "system:serviceaccount:kube-system:headlamp"
|
"username": "system:serviceaccount:headlamp:headlamp"
|
||||||
},
|
},
|
||||||
"sourceIPs": ["10.96.0.1"],
|
"sourceIPs": ["10.96.0.1"],
|
||||||
"objectRef": {
|
"objectRef": {
|
||||||
@@ -494,7 +494,7 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
|
|||||||
```bash
|
```bash
|
||||||
# Service account mode
|
# Service account mode
|
||||||
kubectl auth can-i get services/proxy \
|
kubectl auth can-i get services/proxy \
|
||||||
--as=system:serviceaccount:kube-system:headlamp \
|
--as=system:serviceaccount:headlamp:headlamp \
|
||||||
-n polaris \
|
-n polaris \
|
||||||
--resource-name=polaris-dashboard
|
--resource-name=polaris-dashboard
|
||||||
|
|
||||||
|
|||||||
+36
-27
@@ -4,7 +4,16 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
|
|||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`) uses either Authentik OIDC or token-based authentication via repository secrets.
|
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
|
### Required GitHub Secrets
|
||||||
|
|
||||||
@@ -12,12 +21,12 @@ Configure these in GitHub repository settings (Settings → Secrets and variable
|
|||||||
|
|
||||||
| Secret | Required | Description |
|
| Secret | Required | Description |
|
||||||
| -------------------- | -------- | -------------------------------------------------------------- |
|
| -------------------- | -------- | -------------------------------------------------------------- |
|
||||||
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to `https://headlamp.animaniacs.farh.net`) |
|
|
||||||
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
|
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
|
||||||
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
|
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
|
||||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
|
||||||
|
|
||||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
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
|
## Running Locally
|
||||||
|
|
||||||
@@ -32,8 +41,8 @@ The default base URL is `https://headlamp.animaniacs.farh.net`. Override with `H
|
|||||||
### Option 2: K8s bearer token (port-forward)
|
### Option 2: K8s bearer token (port-forward)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp)
|
||||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,12 +56,12 @@ HEADLAMP_URL=http://localhost:4466 npm run e2e:headed
|
|||||||
|
|
||||||
| Variable | Required | Default | Description |
|
| Variable | Required | Default | Description |
|
||||||
| -------------------- | -------- | -------------------------------------- | --------------------------------------- |
|
| -------------------- | -------- | -------------------------------------- | --------------------------------------- |
|
||||||
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
||||||
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
||||||
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
||||||
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (fallback auth) |
|
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (auto-generated in CI) |
|
||||||
|
|
||||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
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
|
## What the Tests Validate
|
||||||
|
|
||||||
@@ -134,7 +143,7 @@ cp .env.example .env
|
|||||||
|
|
||||||
# 3. Set environment variables
|
# 3. Set environment variables
|
||||||
export HEADLAMP_URL=https://your-headlamp-instance.com
|
export HEADLAMP_URL=https://your-headlamp-instance.com
|
||||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp)
|
||||||
|
|
||||||
# 4. Run tests
|
# 4. Run tests
|
||||||
npm run e2e
|
npm run e2e
|
||||||
@@ -249,25 +258,25 @@ test('plugin UI adapts to dark mode', async ({ page }) => {
|
|||||||
|
|
||||||
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
|
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
|
||||||
|
|
||||||
### Required Secrets
|
### Architecture
|
||||||
|
|
||||||
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
|
The E2E workflow deploys a **dedicated Headlamp instance** for each test run:
|
||||||
|
|
||||||
- `HEADLAMP_URL` (optional): Headlamp instance URL
|
1. Build plugin (`npm run build`)
|
||||||
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth)
|
2. Create ConfigMap from `dist/` output (`scripts/deploy-e2e-headlamp.sh`)
|
||||||
- OR `HEADLAMP_TOKEN` (for token-based auth)
|
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`)
|
||||||
|
|
||||||
### Workflow Overview
|
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.
|
||||||
|
|
||||||
1. Checkout code
|
### Cluster Prerequisites
|
||||||
2. Setup Node.js 20 with npm cache
|
|
||||||
3. Install dependencies (`npm ci`)
|
One-time setup by a cluster admin:
|
||||||
4. Install Playwright browsers (`chromium` only)
|
|
||||||
5. Run auth setup (creates session in `e2e/.auth/state.json`)
|
```bash
|
||||||
6. Run all E2E tests
|
kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
||||||
7. Upload artifacts on failure:
|
```
|
||||||
- `playwright-report/` - HTML test report
|
|
||||||
- `test-results/` - Screenshots, traces, videos
|
|
||||||
|
|
||||||
### Manual Trigger
|
### Manual Trigger
|
||||||
|
|
||||||
|
|||||||
+12
-5
@@ -39,13 +39,20 @@ async function authenticateWithOIDC(page: Page, username: string, password: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function authenticateWithToken(page: Page, token: string): Promise<void> {
|
async function authenticateWithToken(page: Page, token: string): Promise<void> {
|
||||||
// Navigate to login — Headlamp redirects / to /c/main/login
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.waitForURL('**/login');
|
// Headlamp goes to /token directly when no OIDC is configured,
|
||||||
|
// or through /login when OIDC is configured
|
||||||
|
await page.waitForURL(/\/(login|token)$/);
|
||||||
|
|
||||||
// Click the token auth option
|
if (page.url().includes('/login')) {
|
||||||
await page.getByRole('button', { name: /use a token/i }).click();
|
// OIDC login page — click "use a token" to reach token auth.
|
||||||
await page.waitForURL('**/token');
|
// 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
|
// Fill the "ID token" field and submit
|
||||||
await page.getByRole('textbox', { name: /id token/i }).fill(token);
|
await page.getByRole('textbox', { name: /id token/i }).fill(token);
|
||||||
|
|||||||
Generated
+91
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "headlamp-polaris",
|
"name": "headlamp-polaris",
|
||||||
"version": "0.7.1",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "headlamp-polaris",
|
"name": "headlamp-polaris",
|
||||||
"version": "0.7.1",
|
"version": "1.0.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||||
@@ -17,11 +17,13 @@
|
|||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"tar": "^7.5.11",
|
"tar": "^7.5.11",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
"undici": "^7.24.3",
|
"undici": "^7.24.3",
|
||||||
"vitest": "^3.0.5"
|
"vitest": "^3.0.5"
|
||||||
},
|
},
|
||||||
@@ -37,6 +39,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@ampproject/remapping": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||||
"version": "11.7.2",
|
"version": "11.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz",
|
||||||
@@ -431,6 +447,16 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@bcoe/v8-coverage": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@bundled-es-modules/cookie": {
|
"node_modules/@bundled-es-modules/cookie": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
|
||||||
@@ -4846,6 +4872,40 @@
|
|||||||
"vitest": "3.2.4"
|
"vitest": "3.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/coverage-v8": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ampproject/remapping": "^2.3.0",
|
||||||
|
"@bcoe/v8-coverage": "^1.0.2",
|
||||||
|
"ast-v8-to-istanbul": "^0.3.3",
|
||||||
|
"debug": "^4.4.1",
|
||||||
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
|
"istanbul-lib-report": "^3.0.1",
|
||||||
|
"istanbul-lib-source-maps": "^5.0.6",
|
||||||
|
"istanbul-reports": "^3.1.7",
|
||||||
|
"magic-string": "^0.30.17",
|
||||||
|
"magicast": "^0.3.5",
|
||||||
|
"std-env": "^3.9.0",
|
||||||
|
"test-exclude": "^7.0.1",
|
||||||
|
"tinyrainbow": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vitest/browser": "3.2.4",
|
||||||
|
"vitest": "3.2.4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vitest/browser": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||||
@@ -5651,6 +5711,35 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ast-v8-to-istanbul": {
|
||||||
|
"version": "0.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
|
||||||
|
"integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.31",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"js-tokens": "^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ast-v8-to-istanbul/node_modules/estree-walker": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/astral-regex": {
|
"node_modules/astral-regex": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
||||||
|
|||||||
+13
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "headlamp-polaris",
|
"name": "headlamp-polaris",
|
||||||
"version": "0.7.1",
|
"version": "1.0.0",
|
||||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
|
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
|
||||||
"author": "privilegedescalation",
|
"author": "privilegedescalation",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"packageManager": "pnpm@10.32.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "headlamp-plugin start",
|
"start": "headlamp-plugin start",
|
||||||
"build": "headlamp-plugin build",
|
"build": "headlamp-plugin build",
|
||||||
@@ -30,9 +31,12 @@
|
|||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"pnpm": {
|
||||||
"tar": "^7.5.11",
|
"overrides": {
|
||||||
"undici": "^7.24.3"
|
"tar": "^7.5.11",
|
||||||
|
"undici": "^7.24.3",
|
||||||
|
"flatted": "^3.4.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||||
@@ -43,11 +47,16 @@
|
|||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
|
"@headlamp-k8s/eslint-config": "^0.6.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"tar": "^7.5.11",
|
"tar": "^7.5.11",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
"undici": "^7.24.3",
|
"undici": "^7.24.3",
|
||||||
"vitest": "^3.0.5"
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+113
-30
@@ -4,10 +4,18 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
tar: ^7.5.11
|
||||||
|
undici: ^7.24.3
|
||||||
|
flatted: ^3.4.2
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@headlamp-k8s/eslint-config':
|
||||||
|
specifier: ^0.6.0
|
||||||
|
version: 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
'@kinvolk/headlamp-plugin':
|
'@kinvolk/headlamp-plugin':
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
||||||
@@ -32,9 +40,18 @@ importers:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^19.2.3
|
specifier: ^19.2.3
|
||||||
version: 19.2.3(@types/react@19.2.14)
|
version: 19.2.3(@types/react@19.2.14)
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: ^3.2.4
|
||||||
|
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))
|
||||||
|
eslint:
|
||||||
|
specifier: ^8.57.0
|
||||||
|
version: 8.57.1
|
||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^24.0.0
|
specifier: ^24.0.0
|
||||||
version: 24.1.3
|
version: 24.1.3
|
||||||
|
prettier:
|
||||||
|
specifier: ^2.8.8
|
||||||
|
version: 2.8.8
|
||||||
react:
|
react:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
@@ -44,6 +61,15 @@ importers:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.4(react@18.3.1)
|
version: 5.3.4(react@18.3.1)
|
||||||
|
tar:
|
||||||
|
specifier: ^7.5.11
|
||||||
|
version: 7.5.12
|
||||||
|
typescript:
|
||||||
|
specifier: ~5.6.2
|
||||||
|
version: 5.6.2
|
||||||
|
undici:
|
||||||
|
specifier: ^7.24.3
|
||||||
|
version: 7.24.5
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
|
||||||
@@ -53,6 +79,10 @@ packages:
|
|||||||
'@adobe/css-tools@4.4.4':
|
'@adobe/css-tools@4.4.4':
|
||||||
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
|
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
|
||||||
|
|
||||||
|
'@ampproject/remapping@2.3.0':
|
||||||
|
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
'@apidevtools/json-schema-ref-parser@11.7.2':
|
'@apidevtools/json-schema-ref-parser@11.7.2':
|
||||||
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
|
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -159,6 +189,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2':
|
||||||
|
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@bundled-es-modules/cookie@2.0.1':
|
'@bundled-es-modules/cookie@2.0.1':
|
||||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||||
|
|
||||||
@@ -1599,6 +1633,15 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vitest: 3.2.4
|
vitest: 3.2.4
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@3.2.4':
|
||||||
|
resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@vitest/browser': 3.2.4
|
||||||
|
vitest: 3.2.4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitest/expect@3.2.4':
|
'@vitest/expect@3.2.4':
|
||||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
@@ -1845,6 +1888,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@0.3.12:
|
||||||
|
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
|
||||||
|
|
||||||
astral-regex@2.0.0:
|
astral-regex@2.0.0:
|
||||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2824,8 +2870,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
|
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
|
|
||||||
flatted@3.4.0:
|
flatted@3.4.2:
|
||||||
resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==}
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
for-each@0.3.5:
|
for-each@0.3.5:
|
||||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||||
@@ -3390,6 +3436,9 @@ packages:
|
|||||||
js-base64@3.7.8:
|
js-base64@3.7.8:
|
||||||
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
||||||
|
|
||||||
|
js-tokens@10.0.0:
|
||||||
|
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -4727,8 +4776,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
tar@7.5.10:
|
tar@7.5.12:
|
||||||
resolution: {integrity: sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==}
|
resolution: {integrity: sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
teex@1.0.1:
|
teex@1.0.1:
|
||||||
@@ -4912,8 +4961,8 @@ packages:
|
|||||||
undici-types@6.21.0:
|
undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
undici@7.22.0:
|
undici@7.24.5:
|
||||||
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
|
resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
unicorn-magic@0.1.0:
|
unicorn-magic@0.1.0:
|
||||||
@@ -5312,6 +5361,11 @@ snapshots:
|
|||||||
|
|
||||||
'@adobe/css-tools@4.4.4': {}
|
'@adobe/css-tools@4.4.4': {}
|
||||||
|
|
||||||
|
'@ampproject/remapping@2.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
'@apidevtools/json-schema-ref-parser@11.7.2':
|
'@apidevtools/json-schema-ref-parser@11.7.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jsdevtools/ono': 7.1.3
|
'@jsdevtools/ono': 7.1.3
|
||||||
@@ -5455,6 +5509,8 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.27.1
|
'@babel/helper-string-parser': 7.27.1
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
||||||
'@bundled-es-modules/cookie@2.0.1':
|
'@bundled-es-modules/cookie@2.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
@@ -5546,7 +5602,7 @@ snapshots:
|
|||||||
|
|
||||||
'@emotion/sheet@1.4.0': {}
|
'@emotion/sheet@1.4.0': {}
|
||||||
|
|
||||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.28.6
|
||||||
'@emotion/babel-plugin': 11.13.5
|
'@emotion/babel-plugin': 11.13.5
|
||||||
@@ -5717,7 +5773,7 @@ snapshots:
|
|||||||
js-yaml: 4.1.1
|
js-yaml: 4.1.1
|
||||||
semver: 7.7.4
|
semver: 7.7.4
|
||||||
table: 6.9.0
|
table: 6.9.0
|
||||||
tar: 7.5.10
|
tar: 7.5.12
|
||||||
tmp: 0.2.5
|
tmp: 0.2.5
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
|
||||||
@@ -5835,18 +5891,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
|
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@headlamp-k8s/eslint-config': 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
'@headlamp-k8s/eslint-config': 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
'@headlamp-k8s/pluginctl': 0.1.1
|
'@headlamp-k8s/pluginctl': 0.1.1
|
||||||
'@iconify/icons-mdi': 1.2.48
|
'@iconify/icons-mdi': 1.2.48
|
||||||
'@iconify/react': 3.2.2(react@18.3.1)
|
'@iconify/react': 3.2.2(react@18.3.1)
|
||||||
'@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@mui/icons-material': 5.18.0(@mui/material@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))(@types/react@19.2.14)(react@18.3.1)
|
'@mui/icons-material': 5.18.0(@mui/material@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))(@types/react@19.2.14)(react@18.3.1)
|
||||||
'@mui/lab': 5.0.0-alpha.177(@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@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))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/lab': 5.0.0-alpha.177(@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))(@mui/material@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))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@mui/material': 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@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/material': 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@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@mui/system': 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@18.3.28)(react@18.3.1)
|
'@mui/system': 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@18.3.28)(react@18.3.1)
|
||||||
'@mui/x-date-pickers': 7.29.4(@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))(@mui/material@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))(@mui/system@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@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/x-date-pickers': 7.29.4(@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))(@mui/material@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))(@mui/system@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@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@mui/x-tree-view': 6.17.0(@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@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))(@mui/system@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/x-tree-view': 6.17.0(@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))(@mui/material@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))(@mui/system@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@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)
|
'@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)
|
||||||
'@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
'@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
||||||
'@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
'@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
||||||
@@ -5900,7 +5956,7 @@ snapshots:
|
|||||||
jsdom: 24.1.3
|
jsdom: 24.1.3
|
||||||
jsonpath-plus: 10.4.0
|
jsonpath-plus: 10.4.0
|
||||||
lodash: 4.17.23
|
lodash: 4.17.23
|
||||||
material-react-table: 2.13.3(9c8771ba98ea800c4ce3ae047fad2581)
|
material-react-table: 2.13.3(6e12a7d949eb369c0813bc8d1756414b)
|
||||||
monaco-editor: 0.52.2
|
monaco-editor: 0.52.2
|
||||||
msw: 2.4.9(typescript@5.6.2)
|
msw: 2.4.9(typescript@5.6.2)
|
||||||
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2))
|
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2))
|
||||||
@@ -5925,7 +5981,7 @@ snapshots:
|
|||||||
spacetime: 7.12.0
|
spacetime: 7.12.0
|
||||||
storybook: 9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))
|
storybook: 9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))
|
||||||
table: 6.9.0
|
table: 6.9.0
|
||||||
tar: 7.5.10
|
tar: 7.5.12
|
||||||
ts-loader: 9.5.4(typescript@5.6.2)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
ts-loader: 9.5.4(typescript@5.6.2)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
||||||
typescript: 5.6.2
|
typescript: 5.6.2
|
||||||
validate-npm-package-name: 3.0.0
|
validate-npm-package-name: 3.0.0
|
||||||
@@ -6046,7 +6102,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@mui/lab@5.0.0-alpha.177(@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@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))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@mui/lab@5.0.0-alpha.177(@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))(@mui/material@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))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.28.6
|
||||||
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -6060,7 +6116,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@types/react': 18.3.28
|
'@types/react': 18.3.28
|
||||||
|
|
||||||
'@mui/material@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@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@mui/material@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@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
@@ -6081,7 +6137,7 @@ snapshots:
|
|||||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@types/react': 18.3.28
|
'@types/react': 18.3.28
|
||||||
|
|
||||||
'@mui/material@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)':
|
'@mui/material@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)':
|
||||||
@@ -6102,7 +6158,7 @@ snapshots:
|
|||||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)':
|
'@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)':
|
||||||
@@ -6133,7 +6189,7 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
|
|
||||||
'@mui/system@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@18.3.28)(react@18.3.1)':
|
'@mui/system@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@18.3.28)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6148,7 +6204,7 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@types/react': 18.3.28
|
'@types/react': 18.3.28
|
||||||
|
|
||||||
'@mui/system@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@18.3.1)':
|
'@mui/system@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@18.3.1)':
|
||||||
@@ -6164,7 +6220,7 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@mui/types@7.2.24(@types/react@18.3.28)':
|
'@mui/types@7.2.24(@types/react@18.3.28)':
|
||||||
@@ -6244,7 +6300,7 @@ snapshots:
|
|||||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
@@ -6256,11 +6312,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
'@mui/x-tree-view@6.17.0(@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@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))(@mui/system@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@mui/x-tree-view@6.17.0(@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))(@mui/material@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))(@mui/system@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@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.28.6
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@mui/base': 5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/base': 5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@mui/material': 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)
|
'@mui/material': 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)
|
||||||
'@mui/system': 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@18.3.28)(react@18.3.1)
|
'@mui/system': 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@18.3.28)(react@18.3.1)
|
||||||
@@ -7149,6 +7205,25 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))':
|
||||||
|
dependencies:
|
||||||
|
'@ampproject/remapping': 2.3.0
|
||||||
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
|
ast-v8-to-istanbul: 0.3.12
|
||||||
|
debug: 4.4.3
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
istanbul-lib-source-maps: 5.0.6
|
||||||
|
istanbul-reports: 3.2.0
|
||||||
|
magic-string: 0.30.21
|
||||||
|
magicast: 0.3.5
|
||||||
|
std-env: 3.10.0
|
||||||
|
test-exclude: 7.0.2
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@vitest/expect@3.2.4':
|
'@vitest/expect@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
@@ -7468,6 +7543,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@0.3.12:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
js-tokens: 10.0.0
|
||||||
|
|
||||||
astral-regex@2.0.0: {}
|
astral-regex@2.0.0: {}
|
||||||
|
|
||||||
async-function@1.0.0: {}
|
async-function@1.0.0: {}
|
||||||
@@ -7718,7 +7799,7 @@ snapshots:
|
|||||||
parse5: 7.3.0
|
parse5: 7.3.0
|
||||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||||
parse5-parser-stream: 7.1.2
|
parse5-parser-stream: 7.1.2
|
||||||
undici: 7.22.0
|
undici: 7.24.5
|
||||||
whatwg-mimetype: 4.0.0
|
whatwg-mimetype: 4.0.0
|
||||||
|
|
||||||
chokidar@3.6.0:
|
chokidar@3.6.0:
|
||||||
@@ -8632,11 +8713,11 @@ snapshots:
|
|||||||
|
|
||||||
flat-cache@3.2.0:
|
flat-cache@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.4.0
|
flatted: 3.4.2
|
||||||
keyv: 4.5.4
|
keyv: 4.5.4
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
|
|
||||||
flatted@3.4.0: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
for-each@0.3.5:
|
for-each@0.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9306,6 +9387,8 @@ snapshots:
|
|||||||
|
|
||||||
js-base64@3.7.8: {}
|
js-base64@3.7.8: {}
|
||||||
|
|
||||||
|
js-tokens@10.0.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-tokens@9.0.1: {}
|
js-tokens@9.0.1: {}
|
||||||
@@ -9469,10 +9552,10 @@ snapshots:
|
|||||||
'@types/minimatch': 3.0.5
|
'@types/minimatch': 3.0.5
|
||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
|
|
||||||
material-react-table@2.13.3(9c8771ba98ea800c4ce3ae047fad2581):
|
material-react-table@2.13.3(6e12a7d949eb369c0813bc8d1756414b):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
'@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@18.3.28)(react@18.3.1))(@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)
|
||||||
'@mui/icons-material': 5.18.0(@mui/material@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))(@types/react@19.2.14)(react@18.3.1)
|
'@mui/icons-material': 5.18.0(@mui/material@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))(@types/react@19.2.14)(react@18.3.1)
|
||||||
'@mui/material': 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)
|
'@mui/material': 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)
|
||||||
'@mui/x-date-pickers': 7.29.4(@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))(@mui/material@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))(@mui/system@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@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@mui/x-date-pickers': 7.29.4(@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))(@mui/material@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))(@mui/system@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@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -10975,7 +11058,7 @@ snapshots:
|
|||||||
|
|
||||||
tapable@2.3.0: {}
|
tapable@2.3.0: {}
|
||||||
|
|
||||||
tar@7.5.10:
|
tar@7.5.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@isaacs/fs-minipass': 4.0.1
|
'@isaacs/fs-minipass': 4.0.1
|
||||||
chownr: 3.0.0
|
chownr: 3.0.0
|
||||||
@@ -11182,7 +11265,7 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
undici@7.22.0: {}
|
undici@7.24.5: {}
|
||||||
|
|
||||||
unicorn-magic@0.1.0: {}
|
unicorn-magic@0.1.0: {}
|
||||||
|
|
||||||
|
|||||||
+2
-16
@@ -1,19 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:recommended"],
|
"extends": ["github>privilegedescalation/.github:renovate-config"]
|
||||||
"baseBranches": ["main"],
|
|
||||||
"schedule": ["every weekend"],
|
|
||||||
"prConcurrentLimit": 10,
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchManagers": ["npm"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
|
||||||
"groupName": "npm minor and patch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchManagers": ["github-actions"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
|
||||||
"groupName": "github-actions minor and patch"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Executable
+212
@@ -0,0 +1,212 @@
|
|||||||
|
#!/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 `privilegedescalation-dev` namespace. Nothing
|
||||||
|
# persists beyond the test run — teardown cleans up all created resources.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Plugin built (dist/ exists with plugin-main.js + package.json)
|
||||||
|
# - kubectl configured with cluster access
|
||||||
|
# RBAC is managed via Flux from privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml.
|
||||||
|
# The infra repo is the source of truth — do not apply this file directly.
|
||||||
|
# Apply RBAC first: kubectl apply -f privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# E2E_NAMESPACE — namespace for E2E Headlamp (default: privilegedescalation-dev)
|
||||||
|
# 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:-privilegedescalation-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 privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-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 - <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: ${E2E_RELEASE}
|
||||||
|
namespace: ${E2E_NAMESPACE}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ${E2E_RELEASE}
|
||||||
|
namespace: ${E2E_NAMESPACE}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: headlamp
|
||||||
|
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: headlamp
|
||||||
|
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: headlamp
|
||||||
|
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: ${E2E_RELEASE}
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
securityContext: {}
|
||||||
|
containers:
|
||||||
|
- name: headlamp
|
||||||
|
image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
privileged: false
|
||||||
|
runAsUser: 100
|
||||||
|
runAsGroup: 101
|
||||||
|
args:
|
||||||
|
- "-in-cluster"
|
||||||
|
- "-in-cluster-context-name=main"
|
||||||
|
- "-plugins-dir=/headlamp/plugins"
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 4466
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: polaris-plugin
|
||||||
|
mountPath: /headlamp/plugins/headlamp-polaris
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: polaris-plugin
|
||||||
|
configMap:
|
||||||
|
name: headlamp-polaris-plugin
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ${E2E_RELEASE}
|
||||||
|
namespace: ${E2E_NAMESPACE}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: headlamp
|
||||||
|
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: headlamp
|
||||||
|
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Waiting for rollout..."
|
||||||
|
kubectl rollout status "deployment/${E2E_RELEASE}" \
|
||||||
|
-n "$E2E_NAMESPACE" --timeout=120s
|
||||||
|
|
||||||
|
# --- Generate a service URL for tests ---
|
||||||
|
SVC_URL="http://${E2E_RELEASE}.${E2E_NAMESPACE}.svc.cluster.local"
|
||||||
|
|
||||||
|
# --- Wait for DNS and HTTP reachability ---
|
||||||
|
# rollout status only confirms the pod is ready per readinessProbe.
|
||||||
|
# Kubernetes Service DNS may still be propagating to the runner pod.
|
||||||
|
# Poll until the service is reachable over HTTP before handing off.
|
||||||
|
echo ""
|
||||||
|
echo "Waiting for ${SVC_URL} to be reachable..."
|
||||||
|
ATTEMPTS=0
|
||||||
|
MAX_ATTEMPTS=24 # 24 × 5s = 120s max
|
||||||
|
until curl -sf --max-time 5 "${SVC_URL}" -o /dev/null 2>/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=<generated>"
|
||||||
|
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."
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# deploy-plugin-via-volume.sh
|
|
||||||
#
|
|
||||||
# Copies the built plugin into the shared PVC so Headlamp picks it up.
|
|
||||||
# Uses a temporary Kubernetes Job to write to the PVC — the CI runner
|
|
||||||
# does NOT need the PVC mounted locally.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# scripts/deploy-plugin-via-volume.sh
|
|
||||||
#
|
|
||||||
# Environment:
|
|
||||||
# HEADLAMP_NAMESPACE — namespace where Headlamp runs (default: kube-system)
|
|
||||||
# HEADLAMP_DEPLOY — Headlamp deployment name (default: headlamp)
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
HEADLAMP_NAMESPACE="${HEADLAMP_NAMESPACE:-kube-system}"
|
|
||||||
HEADLAMP_DEPLOY="${HEADLAMP_DEPLOY:-headlamp}"
|
|
||||||
|
|
||||||
# The deployed directory name must match the package.json name and
|
|
||||||
# the registerPluginSettings name. Headlamp identifies plugins by
|
|
||||||
# reading package.json from each subdirectory of the plugins dir.
|
|
||||||
PLUGIN_DIR_NAME="headlamp-polaris"
|
|
||||||
DIST_DIR="$REPO_ROOT/dist"
|
|
||||||
|
|
||||||
if [ ! -d "$DIST_DIR" ]; then
|
|
||||||
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Deploying plugin to shared volume via temporary job..."
|
|
||||||
echo " Source: $DIST_DIR"
|
|
||||||
echo " PVC: headlamp-plugins"
|
|
||||||
echo " Plugin: $PLUGIN_DIR_NAME"
|
|
||||||
|
|
||||||
# Create tarball of plugin dist + package.json
|
|
||||||
TAR_FILE=$(mktemp /tmp/plugin-XXXXXX.tar.gz)
|
|
||||||
tar -czf "$TAR_FILE" -C "$DIST_DIR" . -C "$REPO_ROOT" package.json
|
|
||||||
echo " Tarball: $TAR_FILE ($(du -h "$TAR_FILE" | cut -f1))"
|
|
||||||
|
|
||||||
# Find the node where Headlamp is running — the PVC is ReadWriteOnce so
|
|
||||||
# the deploy job must land on the same node to mount it.
|
|
||||||
HEADLAMP_NODE=$(kubectl get pods -n "$HEADLAMP_NAMESPACE" \
|
|
||||||
-l "app.kubernetes.io/name=headlamp" \
|
|
||||||
-o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || true)
|
|
||||||
if [ -z "$HEADLAMP_NODE" ]; then
|
|
||||||
HEADLAMP_NODE=$(kubectl get pods -n "$HEADLAMP_NAMESPACE" \
|
|
||||||
-l "app.kubernetes.io/instance=headlamp" \
|
|
||||||
-o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || true)
|
|
||||||
fi
|
|
||||||
if [ -n "$HEADLAMP_NODE" ]; then
|
|
||||||
echo " Headlamp node: $HEADLAMP_NODE (scheduling deploy job there)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up any previous deploy resources
|
|
||||||
kubectl delete pod plugin-deploy -n "$HEADLAMP_NAMESPACE" --ignore-not-found --wait=true 2>/dev/null || true
|
|
||||||
kubectl delete configmap plugin-tarball -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Store the tarball in a ConfigMap (binary-safe via --from-file)
|
|
||||||
echo "Creating ConfigMap with plugin tarball..."
|
|
||||||
kubectl create configmap plugin-tarball \
|
|
||||||
-n "$HEADLAMP_NAMESPACE" \
|
|
||||||
--from-file=plugin.tar.gz="$TAR_FILE"
|
|
||||||
|
|
||||||
# Build the Pod manifest as a temp file to avoid heredoc YAML escaping issues
|
|
||||||
POD_FILE=$(mktemp /tmp/plugin-deploy-pod-XXXXXX.yaml)
|
|
||||||
|
|
||||||
cat > "$POD_FILE" <<'YAMLDOC'
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: plugin-deploy
|
|
||||||
spec:
|
|
||||||
restartPolicy: Never
|
|
||||||
containers:
|
|
||||||
- name: deploy
|
|
||||||
image: busybox:1.36
|
|
||||||
command: ["sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
echo "Cleaning up stale plugin directories..."
|
|
||||||
rm -rf /plugins/polaris /plugins/headlamp-polaris
|
|
||||||
echo "Extracting plugin to shared volume..."
|
|
||||||
mkdir -p /plugins/PLUGIN_DIR_PLACEHOLDER
|
|
||||||
tar -xzf /tarball/plugin.tar.gz -C /plugins/PLUGIN_DIR_PLACEHOLDER
|
|
||||||
echo "Files deployed:"
|
|
||||||
ls -la /plugins/PLUGIN_DIR_PLACEHOLDER/
|
|
||||||
volumeMounts:
|
|
||||||
- name: plugins
|
|
||||||
mountPath: /plugins
|
|
||||||
- name: tarball
|
|
||||||
mountPath: /tarball
|
|
||||||
readOnly: true
|
|
||||||
volumes:
|
|
||||||
- name: plugins
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: headlamp-plugins
|
|
||||||
- name: tarball
|
|
||||||
configMap:
|
|
||||||
name: plugin-tarball
|
|
||||||
YAMLDOC
|
|
||||||
|
|
||||||
# Substitute plugin dir name
|
|
||||||
sed -i "s/PLUGIN_DIR_PLACEHOLDER/${PLUGIN_DIR_NAME}/g" "$POD_FILE"
|
|
||||||
|
|
||||||
# Add nodeName if we know which node Headlamp is on
|
|
||||||
if [ -n "$HEADLAMP_NODE" ]; then
|
|
||||||
sed -i "/restartPolicy: Never/i\\ nodeName: ${HEADLAMP_NODE}" "$POD_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Starting deploy pod..."
|
|
||||||
kubectl apply -n "$HEADLAMP_NAMESPACE" -f "$POD_FILE"
|
|
||||||
rm -f "$POD_FILE"
|
|
||||||
|
|
||||||
# Wait for the pod to complete (Succeeded phase)
|
|
||||||
echo "Waiting for deploy pod to complete..."
|
|
||||||
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/plugin-deploy \
|
|
||||||
-n "$HEADLAMP_NAMESPACE" --timeout=120s
|
|
||||||
|
|
||||||
# Show logs
|
|
||||||
kubectl logs plugin-deploy -n "$HEADLAMP_NAMESPACE" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
kubectl delete pod plugin-deploy -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
|
|
||||||
kubectl delete configmap plugin-tarball -n "$HEADLAMP_NAMESPACE" --ignore-not-found 2>/dev/null || true
|
|
||||||
|
|
||||||
rm -f "$TAR_FILE"
|
|
||||||
|
|
||||||
# Restart Headlamp to pick up the new plugin
|
|
||||||
echo "Restarting Headlamp deployment to load plugin..."
|
|
||||||
kubectl rollout restart "deployment/$HEADLAMP_DEPLOY" -n "$HEADLAMP_NAMESPACE"
|
|
||||||
kubectl rollout status "deployment/$HEADLAMP_DEPLOY" -n "$HEADLAMP_NAMESPACE" --timeout=120s
|
|
||||||
|
|
||||||
echo "Plugin deployed successfully."
|
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# teardown-e2e-headlamp.sh
|
||||||
|
#
|
||||||
|
# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh.
|
||||||
|
#
|
||||||
|
# RBAC is managed via Flux from privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml.
|
||||||
|
# The infra repo is the source of truth — do not apply this file directly.
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# E2E_NAMESPACE — namespace to clean up (default: privilegedescalation-dev)
|
||||||
|
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-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."
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { makeResult } from '../test-utils';
|
||||||
|
|
||||||
|
const { mockApiRequest } = vi.hoisted(() => ({ mockApiRequest: vi.fn() }));
|
||||||
|
|
||||||
|
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||||
|
ApiProxy: { request: mockApiRequest },
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@mui/material/styles', () => ({
|
||||||
|
useTheme: () => ({
|
||||||
|
palette: {
|
||||||
|
primary: { main: '#1976d2', contrastText: '#fff' },
|
||||||
|
action: { disabledBackground: '#e0e0e0', disabled: '#9e9e9e' },
|
||||||
|
divider: '#e0e0e0',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||||
|
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||||
|
<div data-testid="section-box" data-title={title}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||||
|
<span data-testid="status-label" data-status={status}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
Dialog: ({
|
||||||
|
open,
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
title?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}) =>
|
||||||
|
open ? (
|
||||||
|
<div data-testid="dialog" data-title={title}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import ExemptionManager from './ExemptionManager';
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
workloadResult: makeResult(),
|
||||||
|
namespace: 'default',
|
||||||
|
kind: 'Deployment',
|
||||||
|
name: 'my-deploy',
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultWithPodFailures = makeResult({
|
||||||
|
PodResult: {
|
||||||
|
Name: 'pod',
|
||||||
|
Results: {
|
||||||
|
hostIPCSet: {
|
||||||
|
ID: 'hostIPCSet',
|
||||||
|
Message: 'Host IPC is set',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'danger',
|
||||||
|
Category: 'Security',
|
||||||
|
},
|
||||||
|
hostPIDSet: {
|
||||||
|
ID: 'hostPIDSet',
|
||||||
|
Message: 'Host PID is set',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'danger',
|
||||||
|
Category: 'Security',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ContainerResults: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultWithContainerFailures = makeResult({
|
||||||
|
PodResult: {
|
||||||
|
Name: 'pod',
|
||||||
|
Results: {},
|
||||||
|
ContainerResults: [
|
||||||
|
{
|
||||||
|
Name: 'container-1',
|
||||||
|
Results: {
|
||||||
|
cpuRequestsMissing: {
|
||||||
|
ID: 'cpuRequestsMissing',
|
||||||
|
Message: 'CPU requests missing',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'warning',
|
||||||
|
Category: 'Efficiency',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultWithIgnoredFailures = makeResult({
|
||||||
|
PodResult: {
|
||||||
|
Name: 'pod',
|
||||||
|
Results: {
|
||||||
|
hostIPCSet: {
|
||||||
|
ID: 'hostIPCSet',
|
||||||
|
Message: '',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'ignore',
|
||||||
|
Category: 'Security',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ContainerResults: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ExemptionManager', () => {
|
||||||
|
describe('rendering failing checks', () => {
|
||||||
|
it('shows disabled Add Exemption button when no failing checks', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} />);
|
||||||
|
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||||
|
expect(btn).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows enabled Add Exemption button when there are failing checks', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||||
|
expect(btn).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not include ignored-severity checks as failing', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithIgnoredFailures} />);
|
||||||
|
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||||
|
expect(btn).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('collects failing checks from pod-level results', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByText('Host IPC')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Host PID')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('collects failing checks from container-level results', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithContainerFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByText('CPU Requests')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deduplicates checks that appear in multiple containers', () => {
|
||||||
|
const resultWithDuplicate = makeResult({
|
||||||
|
PodResult: {
|
||||||
|
Name: 'pod',
|
||||||
|
Results: {},
|
||||||
|
ContainerResults: [
|
||||||
|
{
|
||||||
|
Name: 'container-1',
|
||||||
|
Results: {
|
||||||
|
cpuRequestsMissing: {
|
||||||
|
ID: 'cpuRequestsMissing',
|
||||||
|
Message: '',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'warning',
|
||||||
|
Category: 'Efficiency',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'container-2',
|
||||||
|
Results: {
|
||||||
|
cpuRequestsMissing: {
|
||||||
|
ID: 'cpuRequestsMissing',
|
||||||
|
Message: '',
|
||||||
|
Details: [],
|
||||||
|
Success: false,
|
||||||
|
Severity: 'warning',
|
||||||
|
Category: 'Efficiency',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithDuplicate} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
const items = screen.getAllByText('CPU Requests');
|
||||||
|
expect(items).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dialog interactions', () => {
|
||||||
|
it('opens dialog when Add Exemption button is clicked', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByTestId('dialog')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes dialog when Cancel button is clicked', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByTestId('dialog')).toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
|
||||||
|
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles individual check selection', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
|
||||||
|
// Find the checkbox next to "Host IPC"
|
||||||
|
const checkboxes = screen.getAllByRole('checkbox');
|
||||||
|
// First checkbox is "Exempt from all checks", rest are individual checks
|
||||||
|
const hostIPCCheckbox = checkboxes[1];
|
||||||
|
expect(hostIPCCheckbox).not.toBeChecked();
|
||||||
|
fireEvent.click(hostIPCCheckbox);
|
||||||
|
expect(hostIPCCheckbox).toBeChecked();
|
||||||
|
fireEvent.click(hostIPCCheckbox);
|
||||||
|
expect(hostIPCCheckbox).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides individual checks list when exempt-all is toggled', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByText('Host IPC')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
|
||||||
|
fireEvent.click(exemptAllCheckbox);
|
||||||
|
expect(screen.queryByText('Host IPC')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Apply button is disabled when no checks selected and exemptAll is false', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
expect(screen.getByRole('button', { name: /apply/i })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Apply button is enabled when exemptAll is checked', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
|
||||||
|
fireEvent.click(exemptAllCheckbox);
|
||||||
|
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Apply button is enabled when at least one individual check is selected', () => {
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
const checkboxes = screen.getAllByRole('checkbox');
|
||||||
|
fireEvent.click(checkboxes[1]); // select first individual check
|
||||||
|
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ApiProxy.request calls', () => {
|
||||||
|
it('patches with exempt-all annotation when exemptAll is selected', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/strategic-merge-patch+json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
metadata: {
|
||||||
|
annotations: { 'polaris.fairwinds.com/exempt': 'true' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('patches with per-check annotations when individual checks selected', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
// Select first check (hostIPCSet)
|
||||||
|
fireEvent.click(screen.getAllByRole('checkbox')[1]);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({
|
||||||
|
metadata: {
|
||||||
|
annotations: { 'polaris.fairwinds.com/hostIPCSet-exempt': 'true' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses core API path for Pod kind (no api group)', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(
|
||||||
|
<ExemptionManager {...defaultProps} kind="Pod" workloadResult={resultWithPodFailures} />
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/namespaces/default/pods/my-deploy',
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses batch API group for Job kind', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(
|
||||||
|
<ExemptionManager {...defaultProps} kind="Job" workloadResult={resultWithPodFailures} />
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/apis/batch/v1/namespaces/default/jobs/my-deploy',
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses batch API group for CronJob kind', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(
|
||||||
|
<ExemptionManager {...defaultProps} kind="CronJob" workloadResult={resultWithPodFailures} />
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/apis/batch/v1/namespaces/default/cronjobs/my-deploy',
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses apps API group for StatefulSet kind', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(
|
||||||
|
<ExemptionManager
|
||||||
|
{...defaultProps}
|
||||||
|
kind="StatefulSet"
|
||||||
|
workloadResult={resultWithPodFailures}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||||
|
'/apis/apps/v1/namespaces/default/statefulsets/my-deploy',
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('feedback states', () => {
|
||||||
|
it('shows success feedback and closes dialog after successful apply', async () => {
|
||||||
|
mockApiRequest.mockResolvedValue({});
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||||
|
const label = screen.getByTestId('status-label');
|
||||||
|
expect(label).toHaveAttribute('data-status', 'success');
|
||||||
|
expect(label).toHaveTextContent('Exemptions applied successfully');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows error feedback and keeps dialog closed after failed apply', async () => {
|
||||||
|
mockApiRequest.mockRejectedValue(new Error('403 Forbidden'));
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const label = screen.getByTestId('status-label');
|
||||||
|
expect(label).toHaveAttribute('data-status', 'error');
|
||||||
|
expect(label).toHaveTextContent(/failed to apply exemptions/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "Applying..." text on Apply button while in-flight', async () => {
|
||||||
|
let resolveRequest!: () => void;
|
||||||
|
mockApiRequest.mockReturnValue(
|
||||||
|
new Promise<void>(res => {
|
||||||
|
resolveRequest = res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||||
|
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: /applying/i })).toBeInTheDocument();
|
||||||
|
resolveRequest();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,5 +9,16 @@ export default defineConfig({
|
|||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
setupFiles: ['./vitest.setup.ts'],
|
setupFiles: ['./vitest.setup.ts'],
|
||||||
exclude: ['e2e/**', 'node_modules/**'],
|
exclude: ['e2e/**', 'node_modules/**'],
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
include: ['src/**/*.{ts,tsx}'],
|
||||||
|
exclude: ['src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/index.tsx'],
|
||||||
|
thresholds: {
|
||||||
|
lines: 80,
|
||||||
|
functions: 80,
|
||||||
|
branches: 80,
|
||||||
|
statements: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user