Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b275a597e7 |
@@ -1,20 +0,0 @@
|
||||
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 }}
|
||||
+45
-55
@@ -7,61 +7,63 @@ on:
|
||||
branches: [main]
|
||||
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:
|
||||
E2E_NAMESPACE: privilegedescalation-dev
|
||||
E2E_RELEASE: headlamp-e2e
|
||||
# Pin to a known-good Headlamp version. Using :latest is risky because
|
||||
# the tag can change between CI runs, causing flaky failures when a newer
|
||||
# image is pulled on some nodes but not others (IfNotPresent pull policy).
|
||||
# Update this when Headlamp is upgraded in production (kube-system).
|
||||
HEADLAMP_VERSION: v0.40.1
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: runners-privilegedescalation
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Deploy E2E Headlamp instance
|
||||
run: scripts/deploy-e2e-headlamp.sh
|
||||
|
||||
- name: Load E2E environment
|
||||
- name: Preflight — verify Headlamp and plugin version
|
||||
env:
|
||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
||||
run: |
|
||||
if [ -f .env.e2e ]; then
|
||||
cat .env.e2e >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
|
||||
EXPECTED=$(node -p "require('./package.json').version")
|
||||
PLUGIN_NAME=$(node -p "require('./package.json').artifacthub?.name || require('./package.json').name")
|
||||
echo "Expected: $PLUGIN_NAME@$EXPECTED"
|
||||
|
||||
# Check Headlamp connectivity
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "$HEADLAMP_URL" || true)
|
||||
if [ "$HTTP_CODE" = "000" ]; then
|
||||
echo "::error::Cannot reach Headlamp at $HEADLAMP_URL"
|
||||
exit 1
|
||||
fi
|
||||
echo "Headlamp responded HTTP $HTTP_CODE"
|
||||
|
||||
# Check installed plugins and version match
|
||||
PLUGIN_JSON=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null || echo "[]")
|
||||
node -e "
|
||||
const expected = '$EXPECTED';
|
||||
const pluginName = '$PLUGIN_NAME';
|
||||
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 === pluginName || p.name === 'polaris' || p.name.includes('polaris'));
|
||||
if (!ours) {
|
||||
console.log('::warning::Plugin ' + pluginName + ' not found in Headlamp — data-dependent tests will fail');
|
||||
} else {
|
||||
console.log('Found plugin: ' + ours.name + ' at path ' + ours.path);
|
||||
}
|
||||
" "$PLUGIN_JSON"
|
||||
|
||||
# Fetch deployed plugin version from package.json
|
||||
DEPLOYED_VERSION=$(curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins/$PLUGIN_NAME/package.json" 2>/dev/null \
|
||||
| node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo "unknown")
|
||||
echo "Deployed version: $DEPLOYED_VERSION"
|
||||
if [ "$DEPLOYED_VERSION" != "$EXPECTED" ] && [ "$DEPLOYED_VERSION" != "unknown" ]; then
|
||||
echo "::warning::Version mismatch — repo has $EXPECTED but Headlamp runs $DEPLOYED_VERSION. Tests may fail due to stale plugin."
|
||||
fi
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
@@ -69,25 +71,13 @@ jobs:
|
||||
- name: Run E2E tests
|
||||
run: npm run e2e
|
||||
env:
|
||||
HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
|
||||
HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
|
||||
|
||||
- name: Collect deployment diagnostics on failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "=== Pod state ==="
|
||||
kubectl get pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
|
||||
echo "=== Pod describe ==="
|
||||
kubectl describe pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
|
||||
echo "=== Recent namespace events ==="
|
||||
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -20 || true
|
||||
|
||||
- name: Teardown E2E instance
|
||||
if: always()
|
||||
run: scripts/teardown-e2e-headlamp.sh
|
||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
||||
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }}
|
||||
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
|
||||
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -95,7 +85,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results
|
||||
|
||||
@@ -15,9 +15,6 @@ permissions:
|
||||
jobs:
|
||||
release:
|
||||
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:
|
||||
version: ${{ inputs.version }}
|
||||
upstream-repo: 'FairwindsOps/polaris'
|
||||
|
||||
@@ -6,6 +6,5 @@ e2e/.auth/
|
||||
test-results/
|
||||
.playwright-mcp/
|
||||
.env
|
||||
.env.e2e
|
||||
.env.local
|
||||
.eslintcache
|
||||
|
||||
+1
-27
@@ -7,31 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [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
|
||||
|
||||
### Fixed
|
||||
@@ -295,8 +270,7 @@ now frozen — no breaking changes without a new major version.
|
||||
- Automated release workflow
|
||||
- Basic CI/CD pipeline
|
||||
|
||||
[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
|
||||
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.6.0...HEAD
|
||||
[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.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
|
||||
|
||||
@@ -48,14 +48,9 @@ Polaris must be deployed in the `polaris` namespace with the dashboard component
|
||||
|
||||
## Installing
|
||||
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Install via the Headlamp UI:
|
||||
### Option 1: Headlamp Plugin Manager (Recommended)
|
||||
|
||||
1. Go to **Settings → Plugins**
|
||||
2. Click **Catalog** tab
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install**
|
||||
|
||||
Or configure Headlamp via Helm:
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp via Helm:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
@@ -67,6 +62,56 @@ pluginsManager:
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
Or install via the Headlamp UI:
|
||||
|
||||
1. Go to **Settings → Plugins**
|
||||
2. Click **Catalog** tab
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install**
|
||||
|
||||
### Option 2: Sidecar Container (Alternative)
|
||||
|
||||
For detailed sidecar installation instructions, see [docs/DEPLOYMENT.md#installation-method-2-sidecar-container](docs/DEPLOYMENT.md#installation-method-2-sidecar-container).
|
||||
|
||||
```yaml
|
||||
sidecars:
|
||||
- name: headlamp-plugin
|
||||
image: node:lts-alpine
|
||||
command: ['/bin/sh']
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml
|
||||
tail -f /dev/null
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
```
|
||||
|
||||
### Option 3: Manual Tarball Install
|
||||
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/privilegedescalation/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
|
||||
```bash
|
||||
wget https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
tar xzf polaris-0.3.10.tar.gz -C /headlamp/plugins/
|
||||
```
|
||||
|
||||
### Option 4: Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
npm install
|
||||
npm run build
|
||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||
```
|
||||
|
||||
For complete installation instructions including Helm integration, FluxCD examples, and production deployment checklist, see **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)**.
|
||||
|
||||
## RBAC / Security Setup
|
||||
|
||||
The plugin fetches audit data through the Kubernetes API server's **service proxy** sub-resource. The identity making the request (Headlamp's service account, or the user's own token in token-auth mode) must be granted:
|
||||
@@ -97,7 +142,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp # adjust to match your Headlamp service account
|
||||
namespace: headlamp # adjust to match the namespace Headlamp runs in
|
||||
namespace: kube-system # adjust to match the namespace Headlamp runs in
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -197,7 +242,7 @@ npm test
|
||||
npm run test:watch
|
||||
|
||||
# E2E tests (Playwright)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
npm run e2e
|
||||
npm run e2e:headed # see browser
|
||||
```
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
|
||||
+4
-46
@@ -1,4 +1,4 @@
|
||||
version: "1.0.0"
|
||||
version: "0.7.1"
|
||||
name: headlamp-polaris
|
||||
displayName: Polaris
|
||||
createdAt: "2026-02-05T19:00:00Z"
|
||||
@@ -11,7 +11,6 @@ description: >-
|
||||
`polaris-dashboard` service in the `polaris` namespace.
|
||||
license: Apache-2.0
|
||||
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
appVersion: "10.1.6"
|
||||
category: security
|
||||
keywords:
|
||||
- polaris
|
||||
@@ -25,52 +24,11 @@ links:
|
||||
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
- name: Polaris
|
||||
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:
|
||||
- name: privilegedescalation
|
||||
email: "chris@farhood.org"
|
||||
annotations:
|
||||
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/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.1/headlamp-polaris-0.7.1.tar.gz"
|
||||
headlamp/plugin/version-compat: ">=0.26"
|
||||
headlamp/plugin/archive-checksum: sha256:a165e871b40f11a44950aa9f10eb7f7883276f749026ae7a4f886278ecd9bd7d
|
||||
headlamp/plugin/distro-compat: "in-cluster,web,desktop"
|
||||
headlamp/plugin/archive-checksum: sha256:5ea27f3beeab9730a13a752a0a7c2270eca83dcbbe813a6cabeb6a22fa9d5174
|
||||
headlamp/plugin/distro-compat: in-cluster
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
# RBAC for the GitHub Actions CI runner to manage E2E Headlamp instances.
|
||||
# CI-only test fixture — NOT for production use.
|
||||
#
|
||||
# This file is a REFERENCE ONLY. The canonical manifest lives in:
|
||||
# privilegedescalation/infra/base/rbac/e2e-ci-runner-headlamp-rbac.yaml
|
||||
#
|
||||
# The infra repo is managed by Flux GitOps and is the source of truth.
|
||||
# Do not apply this file directly — it is kept here for developer reference only.
|
||||
#
|
||||
# E2E resources run in `privilegedescalation-dev` — nothing persists beyond a test run.
|
||||
# RBAC is managed via Flux from privilegedescalation/infra — do not apply manually.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
# Custom Headlamp values for static plugin installation
|
||||
# This disables the plugin manager and uses an init container instead
|
||||
|
||||
# Disable the plugin manager sidecar
|
||||
pluginsManager:
|
||||
enabled: false
|
||||
|
||||
# Use an init container to install plugins to /headlamp/static-plugins
|
||||
initContainers:
|
||||
- name: install-plugins
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Installing plugins to /headlamp/static-plugins..."
|
||||
|
||||
# Create plugins directory
|
||||
mkdir -p /headlamp/static-plugins
|
||||
|
||||
# Set up npm cache
|
||||
export NPM_CONFIG_CACHE=/tmp/npm-cache
|
||||
export NPM_CONFIG_USERCONFIG=/tmp/npm-userconfig
|
||||
mkdir -p /tmp/npm-cache /tmp/npm-userconfig
|
||||
|
||||
# Install polaris plugin
|
||||
echo "Installing polaris plugin..."
|
||||
cd /headlamp/static-plugins
|
||||
npm pack headlamp-polaris-plugin@0.3.0
|
||||
tar -xzf headlamp-polaris-plugin-0.3.0.tgz
|
||||
mv package headlamp-polaris-plugin
|
||||
rm headlamp-polaris-plugin-0.3.0.tgz
|
||||
|
||||
# Install other plugins
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_flux \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-trivy/headlamp_trivy \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_cert-manager \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_ai_assistant \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
echo "All plugins installed successfully"
|
||||
ls -la /headlamp/static-plugins
|
||||
securityContext:
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
|
||||
# Configure headlamp to use static plugins
|
||||
config:
|
||||
pluginsDir: /headlamp/static-plugins
|
||||
|
||||
# Add volume for static plugins
|
||||
volumes:
|
||||
- name: static-plugins
|
||||
emptyDir: {}
|
||||
|
||||
# Add volume mount to main container
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
readOnly: true
|
||||
+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
|
||||
|
||||
# Verify Headlamp is deployed
|
||||
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
```
|
||||
|
||||
## Installation Methods
|
||||
@@ -59,7 +59,7 @@ kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
|
||||
+3
-2
@@ -268,9 +268,10 @@ npm run e2e
|
||||
|
||||
```bash
|
||||
# Create token
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
|
||||
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||
# Port-forward for local testing
|
||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
||||
|
||||
# Run tests
|
||||
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
|
||||
# View Headlamp pod logs (plugin sidecar)
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
|
||||
# Expected output:
|
||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
||||
@@ -43,7 +43,7 @@ kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
**Verify plugin files exist**:
|
||||
|
||||
```bash
|
||||
kubectl exec -n headlamp deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
# Should show: headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
@@ -118,7 +118,7 @@ Expected subjects:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
For OIDC mode:
|
||||
@@ -154,7 +154,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -169,7 +169,7 @@ Service account mode:
|
||||
```bash
|
||||
# Impersonate Headlamp service account
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes
|
||||
@@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \
|
||||
After applying RBAC changes:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment headlamp -n headlamp
|
||||
kubectl rollout restart deployment headlamp -n kube-system
|
||||
```
|
||||
|
||||
---
|
||||
@@ -490,7 +490,7 @@ Run this script to test all RBAC components:
|
||||
#!/bin/bash
|
||||
NS="polaris"
|
||||
SA="headlamp"
|
||||
SA_NS="headlamp"
|
||||
SA_NS="kube-system"
|
||||
|
||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||
|
||||
@@ -529,8 +529,8 @@ echo "=== Test complete ==="
|
||||
Test connectivity from Headlamp to Polaris:
|
||||
|
||||
```bash
|
||||
# Create debug pod in headlamp namespace
|
||||
kubectl run netdebug -n headlamp --rm -it --image=nicolaka/netshoot -- bash
|
||||
# Create debug pod in kube-system namespace
|
||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
||||
|
||||
# Inside pod, test DNS and HTTP
|
||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||
@@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests:
|
||||
|
||||
```bash
|
||||
# View recent audit logs (location varies by cluster)
|
||||
kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
||||
|
||||
# Look for lines with:
|
||||
# "reason": "Forbidden"
|
||||
# "user": "system:serviceaccount:headlamp:headlamp"
|
||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -567,7 +567,7 @@ kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||
**Check sidecar logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
```
|
||||
|
||||
**Common errors**:
|
||||
@@ -591,7 +591,7 @@ Error: 404 Not Found
|
||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||
|
||||
```bash
|
||||
kubectl get configmap headlamp-plugin-config -n headlamp -o yaml
|
||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
||||
```
|
||||
|
||||
Expected format:
|
||||
@@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue:
|
||||
1. **Version Information**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n headlamp -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
```
|
||||
|
||||
2. **Plugin Version**:
|
||||
|
||||
- Check Settings → Plugins in Headlamp UI
|
||||
- Or: `kubectl exec -n headlamp deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
|
||||
3. **Browser Console Output**:
|
||||
|
||||
@@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue:
|
||||
5. **Pod Logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||
```
|
||||
|
||||
|
||||
+22
-22
@@ -19,7 +19,7 @@ Helm provides the easiest way to deploy and manage the plugin in production. Thi
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo update
|
||||
```
|
||||
|
||||
@@ -41,11 +41,11 @@ pluginsManager:
|
||||
```bash
|
||||
# Install Headlamp
|
||||
helm install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
|
||||
# Wait for deployment
|
||||
kubectl -n headlamp wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
```
|
||||
|
||||
After installation, install the plugin via Headlamp UI (**Settings → Plugins → Catalog**).
|
||||
@@ -131,7 +131,7 @@ Deploy:
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--wait \
|
||||
--timeout 5m
|
||||
@@ -177,7 +177,7 @@ apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
@@ -191,7 +191,7 @@ Apply ConfigMap then deploy Headlamp:
|
||||
kubectl apply -f headlamp-plugin-config.yaml
|
||||
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
@@ -210,7 +210,7 @@ metadata:
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1h
|
||||
url: https://kubernetes-sigs.github.io/headlamp/
|
||||
url: https://headlamp-k8s.github.io/headlamp/
|
||||
```
|
||||
|
||||
### HelmRelease
|
||||
@@ -221,7 +221,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
interval: 30m
|
||||
chart:
|
||||
@@ -300,7 +300,7 @@ kubectl apply -f helmrepository.yaml
|
||||
kubectl apply -f helmrelease.yaml
|
||||
|
||||
# Watch deployment
|
||||
flux get helmreleases -n headlamp --watch
|
||||
flux get helmreleases -n kube-system --watch
|
||||
```
|
||||
|
||||
## RBAC Configuration
|
||||
@@ -329,7 +329,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -349,7 +349,7 @@ helm repo update
|
||||
|
||||
# Upgrade Headlamp (preserves plugin configuration)
|
||||
helm upgrade headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--wait
|
||||
```
|
||||
@@ -365,15 +365,15 @@ helm upgrade headlamp headlamp/headlamp \
|
||||
|
||||
```bash
|
||||
# Update ConfigMap with new version
|
||||
kubectl -n headlamp edit configmap headlamp-plugin-config
|
||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
||||
|
||||
# Update version and URL:
|
||||
# version: 0.3.6
|
||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||
|
||||
# Restart deployment to trigger init container
|
||||
kubectl -n headlamp rollout restart deployment/headlamp
|
||||
kubectl -n headlamp rollout status deployment/headlamp
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
kubectl -n kube-system rollout status deployment/headlamp
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -382,25 +382,25 @@ kubectl -n headlamp rollout status deployment/headlamp
|
||||
|
||||
```bash
|
||||
# Check Headlamp values
|
||||
helm get values headlamp -n headlamp
|
||||
helm get values headlamp -n kube-system
|
||||
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# If missing, reinstall plugin via UI or check init container logs
|
||||
kubectl -n headlamp logs deployment/headlamp -c install-polaris-plugin
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-polaris-plugin
|
||||
```
|
||||
|
||||
### Helm Release Stuck
|
||||
|
||||
```bash
|
||||
# Check Helm release status
|
||||
helm list -n headlamp
|
||||
helm list -n kube-system
|
||||
|
||||
# If stuck, force upgrade
|
||||
helm upgrade headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml \
|
||||
--force \
|
||||
--wait
|
||||
@@ -410,13 +410,13 @@ helm upgrade headlamp headlamp/headlamp \
|
||||
|
||||
```bash
|
||||
# Check HelmRelease status
|
||||
flux get helmreleases -n headlamp
|
||||
flux get helmreleases -n kube-system
|
||||
|
||||
# Check events
|
||||
kubectl -n headlamp describe helmrelease headlamp
|
||||
kubectl -n kube-system describe helmrelease headlamp
|
||||
|
||||
# Force reconciliation
|
||||
flux reconcile helmrelease headlamp -n headlamp
|
||||
flux reconcile helmrelease headlamp -n kube-system
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -47,7 +47,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -71,7 +71,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
@@ -90,7 +90,7 @@ apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/component: plugin-config
|
||||
@@ -109,7 +109,7 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
spec:
|
||||
@@ -194,7 +194,7 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
|
||||
@@ -204,7 +204,7 @@ apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
spec:
|
||||
@@ -235,27 +235,27 @@ kubectl apply -f headlamp-service.yaml
|
||||
kubectl apply -f headlamp-serviceaccount.yaml
|
||||
|
||||
# Wait for deployment to be ready
|
||||
kubectl -n headlamp wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
kubectl -n kube-system wait --for=condition=available deployment/headlamp --timeout=300s
|
||||
```
|
||||
|
||||
### 2. Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check pods are running
|
||||
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
|
||||
|
||||
# Check init container logs
|
||||
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Expected output:
|
||||
# Plugin installation complete
|
||||
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
@@ -273,7 +273,7 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
||||
|
||||
```bash
|
||||
# Port-forward to access locally
|
||||
kubectl -n headlamp port-forward service/headlamp 8080:80
|
||||
kubectl -n kube-system port-forward service/headlamp 8080:80
|
||||
|
||||
# Open browser to http://localhost:8080
|
||||
```
|
||||
@@ -309,7 +309,7 @@ k8s/
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
@@ -401,7 +401,7 @@ spec:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
## Upgrading the Plugin
|
||||
@@ -410,24 +410,24 @@ spec:
|
||||
|
||||
```bash
|
||||
# Edit ConfigMap with new version
|
||||
kubectl -n headlamp edit configmap headlamp-plugin-config
|
||||
kubectl -n kube-system edit configmap headlamp-plugin-config
|
||||
|
||||
# Update version and URL:
|
||||
# version: 0.3.6
|
||||
# url: https://github.com/.../v0.3.6/polaris-0.3.10.tar.gz
|
||||
|
||||
# Restart deployment to trigger init container
|
||||
kubectl -n headlamp rollout restart deployment/headlamp
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
|
||||
# Wait for rollout to complete
|
||||
kubectl -n headlamp rollout status deployment/headlamp
|
||||
kubectl -n kube-system rollout status deployment/headlamp
|
||||
```
|
||||
|
||||
### Verify Upgrade
|
||||
|
||||
```bash
|
||||
# Check init container logs
|
||||
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Verify new version in UI
|
||||
# Navigate to Settings → Plugins in Headlamp
|
||||
@@ -439,7 +439,7 @@ kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||
|
||||
```bash
|
||||
# Check init container logs
|
||||
kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||
kubectl -n kube-system logs deployment/headlamp -c install-plugins
|
||||
|
||||
# Common issues:
|
||||
# 1. Network connectivity to GitHub
|
||||
@@ -451,14 +451,14 @@ kubectl -n headlamp logs deployment/headlamp -c install-plugins
|
||||
|
||||
```bash
|
||||
# Verify HEADLAMP_CONFIG_WATCH_PLUGINS is false
|
||||
kubectl -n headlamp get deployment headlamp -o yaml | grep WATCH_PLUGINS
|
||||
kubectl -n kube-system get deployment headlamp -o yaml | grep WATCH_PLUGINS
|
||||
|
||||
# Expected output:
|
||||
# - name: HEADLAMP_CONFIG_WATCH_PLUGINS
|
||||
# value: "false"
|
||||
|
||||
# If not set or "true", update deployment
|
||||
kubectl -n headlamp edit deployment headlamp
|
||||
kubectl -n kube-system edit deployment headlamp
|
||||
```
|
||||
|
||||
### RBAC Permissions Denied
|
||||
@@ -466,7 +466,7 @@ kubectl -n headlamp edit deployment headlamp
|
||||
```bash
|
||||
# Test RBAC
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--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
|
||||
|
||||
# Verify Headlamp
|
||||
kubectl -n headlamp get deployment headlamp
|
||||
kubectl -n headlamp get svc headlamp
|
||||
kubectl -n kube-system get deployment headlamp
|
||||
kubectl -n kube-system get svc headlamp
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
@@ -60,17 +60,17 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
||||
|
||||
# 2. Verify RBAC permissions
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
# Expected: yes
|
||||
|
||||
# 3. Check Headlamp logs for plugin loading
|
||||
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
# Expected: No errors related to plugin loading
|
||||
|
||||
# 4. Verify plugin files exist
|
||||
kubectl -n headlamp exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
# Expected: dist/, package.json present
|
||||
```
|
||||
|
||||
@@ -160,7 +160,7 @@ spec:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: headlamp
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
@@ -241,7 +241,7 @@ apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: headlamp-pdb
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
@@ -295,7 +295,7 @@ apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -312,10 +312,10 @@ spec:
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
kubectl -n headlamp logs deployment/headlamp -f
|
||||
kubectl -n kube-system logs deployment/headlamp -f
|
||||
|
||||
# Filter for plugin-related logs
|
||||
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
```
|
||||
|
||||
**Polaris Dashboard Logs:**
|
||||
@@ -341,14 +341,14 @@ apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: headlamp-alerts
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
spec:
|
||||
groups:
|
||||
- name: headlamp
|
||||
interval: 30s
|
||||
rules:
|
||||
- alert: HeadlampPodNotReady
|
||||
expr: kube_pod_status_ready{namespace="headlamp", pod=~"headlamp-.*"} == 0
|
||||
expr: kube_pod_status_ready{namespace="kube-system", pod=~"headlamp-.*"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
@@ -422,9 +422,9 @@ If Headlamp or plugin becomes unavailable:
|
||||
2. **Redeploy Headlamp:**
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--values headlamp-values.yaml
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
3. **Reapply RBAC:**
|
||||
@@ -436,7 +436,7 @@ helm upgrade --install headlamp headlamp/headlamp \
|
||||
4. **Verify plugin files:**
|
||||
|
||||
```bash
|
||||
kubectl -n headlamp exec deployment/headlamp -- \
|
||||
kubectl -n kube-system exec deployment/headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
|
||||
@@ -268,9 +268,10 @@ npm run e2e
|
||||
|
||||
```bash
|
||||
# Create token
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp --duration=24h)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
|
||||
|
||||
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||
# Port-forward for local testing
|
||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
||||
|
||||
# Run tests
|
||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||
|
||||
@@ -72,7 +72,7 @@ Deploy or update Headlamp:
|
||||
|
||||
```bash
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
@@ -122,7 +122,7 @@ apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-plugin-config
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
data:
|
||||
plugin.yml: |
|
||||
- name: headlamp-polaris-plugin
|
||||
@@ -138,14 +138,14 @@ kubectl apply -f headlamp-plugin-config.yaml
|
||||
|
||||
# Deploy/update Headlamp with sidecar
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
|
||||
# Verify plugin files
|
||||
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
@@ -270,7 +270,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -284,10 +284,10 @@ See [RBAC Permissions](../user-guide/rbac-permissions.md) for detailed RBAC conf
|
||||
|
||||
```bash
|
||||
# If you updated Helm values or ConfigMaps
|
||||
kubectl -n headlamp rollout restart deployment/headlamp
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
```
|
||||
|
||||
### 3. Clear Browser Cache
|
||||
@@ -312,14 +312,14 @@ kubectl -n headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=hea
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
# drwxr-xr-x dist/
|
||||
# -rw-r--r-- package.json
|
||||
|
||||
# Check Headlamp logs for errors
|
||||
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# Expected: No errors related to plugin loading
|
||||
|
||||
@@ -345,13 +345,13 @@ kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy
|
||||
|
||||
```bash
|
||||
# 1. Verify plugin files exist
|
||||
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected: dist/, package.json present
|
||||
|
||||
# 2. Check Headlamp logs for plugin errors
|
||||
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# 3. Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
|
||||
|
||||
@@ -404,7 +404,7 @@ helm install polaris fairwinds-stable/polaris \
|
||||
```bash
|
||||
# Wait 30 minutes for ArtifactHub sync
|
||||
# Or manually force Headlamp restart:
|
||||
kubectl -n headlamp rollout restart deployment/headlamp
|
||||
kubectl -n kube-system rollout restart deployment/headlamp
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -67,14 +67,14 @@ kubectl -n polaris wait --for=condition=ready pod -l app.kubernetes.io/name=pola
|
||||
|
||||
```bash
|
||||
# Check Headlamp is deployed
|
||||
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# Expected output:
|
||||
# NAME READY STATUS RESTARTS AGE
|
||||
# headlamp-xxxxxxxxxx-xxxxx 1/1 Running 0 1h
|
||||
|
||||
# Check Headlamp version (must be v0.26+)
|
||||
kubectl -n headlamp get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||
kubectl -n kube-system get deployment headlamp -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||
|
||||
# Expected output:
|
||||
# ghcr.io/headlamp-k8s/headlamp:v0.39.0 (or similar)
|
||||
@@ -84,17 +84,17 @@ kubectl -n headlamp get deployment headlamp -o jsonpath='{.spec.template.spec.co
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo update
|
||||
|
||||
# Install Headlamp
|
||||
helm install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--set config.pluginsDir="/headlamp/plugins" \
|
||||
--set pluginsManager.enabled=true
|
||||
|
||||
# Wait for pod to be ready
|
||||
kubectl -n headlamp wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
kubectl -n kube-system wait --for=condition=ready pod -l app.kubernetes.io/name=headlamp --timeout=300s
|
||||
```
|
||||
|
||||
## RBAC Requirements
|
||||
@@ -112,7 +112,7 @@ The plugin requires permissions to access the Polaris dashboard via Kubernetes s
|
||||
```bash
|
||||
# Test if Headlamp service account has permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ EOF
|
||||
|
||||
# Update Headlamp
|
||||
helm upgrade --install headlamp headlamp/headlamp \
|
||||
--namespace headlamp \
|
||||
--namespace kube-system \
|
||||
--values headlamp-values.yaml
|
||||
```
|
||||
|
||||
@@ -70,7 +70,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -111,7 +111,7 @@ EOF
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/dist/
|
||||
|
||||
# Expected output:
|
||||
@@ -119,7 +119,7 @@ kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- \
|
||||
|
||||
# Verify RBAC is correct
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
@@ -185,7 +185,7 @@ Cluster score badge in top navigation:
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec -it deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec -it deployment/headlamp -c headlamp -- \
|
||||
ls /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# 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
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
# Expected output: yes
|
||||
|
||||
# 4. Check Headlamp pod is running
|
||||
kubectl -n headlamp get pods -l app.kubernetes.io/name=headlamp
|
||||
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
|
||||
|
||||
# 5. Check Headlamp logs for plugin errors
|
||||
kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
|
||||
|
||||
# Expected: No errors
|
||||
```
|
||||
@@ -57,7 +57,7 @@ kubectl -n headlamp logs deployment/headlamp | grep -i polaris
|
||||
|
||||
```bash
|
||||
# Verify plugin files exist
|
||||
kubectl -n headlamp exec deployment/headlamp -c headlamp -- \
|
||||
kubectl -n kube-system exec deployment/headlamp -c headlamp -- \
|
||||
ls -la /headlamp/plugins/headlamp-polaris-plugin/
|
||||
|
||||
# Expected output:
|
||||
@@ -76,7 +76,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission (service account mode)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ This guide covers common issues encountered when using the Headlamp Polaris Plug
|
||||
|
||||
```bash
|
||||
# View Headlamp pod logs (plugin sidecar)
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
|
||||
# Expected output:
|
||||
# Installing plugin from https://github.com/.../headlamp-polaris-plugin-X.Y.Z.tar.gz
|
||||
@@ -43,7 +43,7 @@ kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
**Verify plugin files exist**:
|
||||
|
||||
```bash
|
||||
kubectl exec -n headlamp deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
kubectl exec -n kube-system deployment/headlamp -c headlamp -- ls -la /headlamp/plugins/
|
||||
# Should show: headlamp-polaris-plugin/
|
||||
```
|
||||
|
||||
@@ -118,7 +118,7 @@ Expected subjects:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
```
|
||||
|
||||
For OIDC mode:
|
||||
@@ -154,7 +154,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -169,7 +169,7 @@ Service account mode:
|
||||
```bash
|
||||
# Impersonate Headlamp service account
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
--resource-name=polaris-dashboard \
|
||||
-n polaris
|
||||
# Expected: yes
|
||||
@@ -189,7 +189,7 @@ kubectl auth can-i get services/proxy \
|
||||
After applying RBAC changes:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment headlamp -n headlamp
|
||||
kubectl rollout restart deployment headlamp -n kube-system
|
||||
```
|
||||
|
||||
---
|
||||
@@ -490,7 +490,7 @@ Run this script to test all RBAC components:
|
||||
#!/bin/bash
|
||||
NS="polaris"
|
||||
SA="headlamp"
|
||||
SA_NS="headlamp"
|
||||
SA_NS="kube-system"
|
||||
|
||||
echo "=== Testing RBAC for Polaris Plugin ==="
|
||||
|
||||
@@ -529,8 +529,8 @@ echo "=== Test complete ==="
|
||||
Test connectivity from Headlamp to Polaris:
|
||||
|
||||
```bash
|
||||
# Create debug pod in headlamp namespace
|
||||
kubectl run netdebug -n headlamp --rm -it --image=nicolaka/netshoot -- bash
|
||||
# Create debug pod in kube-system namespace
|
||||
kubectl run netdebug -n kube-system --rm -it --image=nicolaka/netshoot -- bash
|
||||
|
||||
# Inside pod, test DNS and HTTP
|
||||
nslookup polaris-dashboard.polaris.svc.cluster.local
|
||||
@@ -545,11 +545,11 @@ If you have audit logging enabled, check for denied requests:
|
||||
|
||||
```bash
|
||||
# View recent audit logs (location varies by cluster)
|
||||
kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||
kubectl logs -n kube-system kube-apiserver-* | grep polaris-dashboard
|
||||
|
||||
# Look for lines with:
|
||||
# "reason": "Forbidden"
|
||||
# "user": "system:serviceaccount:headlamp:headlamp"
|
||||
# "user": "system:serviceaccount:kube-system:headlamp"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -567,7 +567,7 @@ kubectl logs -n headlamp kube-apiserver-* | grep polaris-dashboard
|
||||
**Check sidecar logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp-plugin
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp-plugin
|
||||
```
|
||||
|
||||
**Common errors**:
|
||||
@@ -591,7 +591,7 @@ Error: 404 Not Found
|
||||
**Solution**: Verify `archive-url` in plugin config matches GitHub release:
|
||||
|
||||
```bash
|
||||
kubectl get configmap headlamp-plugin-config -n headlamp -o yaml
|
||||
kubectl get configmap headlamp-plugin-config -n kube-system -o yaml
|
||||
```
|
||||
|
||||
Expected format:
|
||||
@@ -677,13 +677,13 @@ If none of these solutions work, gather debugging information and open an issue:
|
||||
1. **Version Information**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n headlamp -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp -o yaml | grep image:
|
||||
```
|
||||
|
||||
2. **Plugin Version**:
|
||||
|
||||
- Check Settings → Plugins in Headlamp UI
|
||||
- Or: `kubectl exec -n headlamp deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
- Or: `kubectl exec -n kube-system deployment/headlamp -c headlamp -- cat /headlamp/plugins/headlamp-polaris-plugin/package.json`
|
||||
|
||||
3. **Browser Console Output**:
|
||||
|
||||
@@ -698,7 +698,7 @@ If none of these solutions work, gather debugging information and open an issue:
|
||||
5. **Pod Logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n headlamp deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n kube-system deployment/headlamp -c headlamp --tail=100
|
||||
kubectl logs -n polaris deployment/polaris-dashboard --tail=100
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ spec:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: headlamp
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
|
||||
@@ -43,7 +43,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -83,7 +83,7 @@ roleRef:
|
||||
```bash
|
||||
# Test service account (in-cluster mode)
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
```
|
||||
|
||||
@@ -65,7 +65,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp # Adjust to your Headlamp SA name
|
||||
namespace: headlamp # Adjust to Headlamp's namespace
|
||||
namespace: kube-system # Adjust to Headlamp's namespace
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -75,7 +75,7 @@ roleRef:
|
||||
**Adjust for your environment:**
|
||||
|
||||
- `subjects[0].name` - Your Headlamp service account name (often `headlamp`)
|
||||
- `subjects[0].namespace` - Namespace where Headlamp runs (often `headlamp`)
|
||||
- `subjects[0].namespace` - Namespace where Headlamp runs (often `kube-system`)
|
||||
|
||||
### Step 3: Apply and Verify
|
||||
|
||||
@@ -91,7 +91,7 @@ kubectl -n polaris get rolebinding headlamp-polaris-proxy
|
||||
|
||||
# Test permission
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
@@ -109,7 +109,7 @@ In token-auth mode, **each user's own identity** is used for Kubernetes API requ
|
||||
With service account mode:
|
||||
|
||||
- Single RoleBinding grants access to all Headlamp users
|
||||
- Kubernetes sees all requests as `system:serviceaccount:headlamp:headlamp`
|
||||
- Kubernetes sees all requests as `system:serviceaccount:kube-system:headlamp`
|
||||
|
||||
With token-auth mode:
|
||||
|
||||
@@ -267,7 +267,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -281,7 +281,7 @@ metadata:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: polaris-proxy-reader
|
||||
@@ -318,7 +318,7 @@ spec:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: headlamp
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: kube-apiserver
|
||||
@@ -411,7 +411,7 @@ Every plugin data fetch creates a Kubernetes API audit log entry.
|
||||
"level": "Metadata",
|
||||
"verb": "get",
|
||||
"user": {
|
||||
"username": "system:serviceaccount:headlamp:headlamp"
|
||||
"username": "system:serviceaccount:kube-system:headlamp"
|
||||
},
|
||||
"sourceIPs": ["10.96.0.1"],
|
||||
"objectRef": {
|
||||
@@ -494,7 +494,7 @@ If using a log aggregator (e.g., Elasticsearch), create filters to exclude or do
|
||||
```bash
|
||||
# Service account mode
|
||||
kubectl auth can-i get services/proxy \
|
||||
--as=system:serviceaccount:headlamp:headlamp \
|
||||
--as=system:serviceaccount:kube-system:headlamp \
|
||||
-n polaris \
|
||||
--resource-name=polaris-dashboard
|
||||
|
||||
|
||||
+27
-36
@@ -4,16 +4,7 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
|
||||
|
||||
## CI
|
||||
|
||||
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`):
|
||||
|
||||
1. Builds the plugin (`npm run build`)
|
||||
2. Creates a ConfigMap from the built `dist/` output
|
||||
3. Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume
|
||||
4. Generates a ServiceAccount token for test auth
|
||||
5. Runs Playwright tests against the E2E instance
|
||||
6. Tears down the E2E instance
|
||||
|
||||
This approach uses the stock `ghcr.io/headlamp-k8s/headlamp` image with no custom Docker builds. The plugin is loaded via `HEADLAMP_PLUGINS_DIR` volume mount.
|
||||
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.
|
||||
|
||||
### Required GitHub Secrets
|
||||
|
||||
@@ -21,12 +12,12 @@ Configure these in GitHub repository settings (Settings → Secrets and variable
|
||||
|
||||
| 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_PASSWORD` | OIDC | Password for that user |
|
||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
||||
|
||||
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.
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
|
||||
## Running Locally
|
||||
|
||||
@@ -41,8 +32,8 @@ The default base URL is `https://headlamp.animaniacs.farh.net`. Override with `H
|
||||
### Option 2: K8s bearer token (port-forward)
|
||||
|
||||
```bash
|
||||
kubectl port-forward -n headlamp svc/headlamp 4466:80
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp)
|
||||
kubectl port-forward -n kube-system svc/headlamp 4466:80
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
|
||||
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
||||
```
|
||||
|
||||
@@ -56,12 +47,12 @@ HEADLAMP_URL=http://localhost:4466 npm run e2e:headed
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
| -------------------- | -------- | -------------------------------------- | --------------------------------------- |
|
||||
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (auto-generated in CI) |
|
||||
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (fallback auth) |
|
||||
|
||||
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.
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
|
||||
## What the Tests Validate
|
||||
|
||||
@@ -143,7 +134,7 @@ cp .env.example .env
|
||||
|
||||
# 3. Set environment variables
|
||||
export HEADLAMP_URL=https://your-headlamp-instance.com
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n headlamp)
|
||||
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
|
||||
|
||||
# 4. Run tests
|
||||
npm run e2e
|
||||
@@ -258,25 +249,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.
|
||||
|
||||
### Architecture
|
||||
### Required Secrets
|
||||
|
||||
The E2E workflow deploys a **dedicated Headlamp instance** for each test run:
|
||||
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
|
||||
1. Build plugin (`npm run build`)
|
||||
2. Create ConfigMap from `dist/` output (`scripts/deploy-e2e-headlamp.sh`)
|
||||
3. Deploy stock Headlamp via Helm with ConfigMap volume mount
|
||||
4. Run Playwright tests against the E2E instance
|
||||
5. Tear down (`scripts/teardown-e2e-headlamp.sh`)
|
||||
- `HEADLAMP_URL` (optional): Headlamp instance URL
|
||||
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth)
|
||||
- OR `HEADLAMP_TOKEN` (for token-based auth)
|
||||
|
||||
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.
|
||||
### Workflow Overview
|
||||
|
||||
### Cluster Prerequisites
|
||||
|
||||
One-time setup by a cluster admin:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
||||
```
|
||||
1. Checkout code
|
||||
2. Setup Node.js 20 with npm cache
|
||||
3. Install dependencies (`npm ci`)
|
||||
4. Install Playwright browsers (`chromium` only)
|
||||
5. Run auth setup (creates session in `e2e/.auth/state.json`)
|
||||
6. Run all E2E tests
|
||||
7. Upload artifacts on failure:
|
||||
- `playwright-report/` - HTML test report
|
||||
- `test-results/` - Screenshots, traces, videos
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
|
||||
+5
-12
@@ -39,20 +39,13 @@ async function authenticateWithOIDC(page: Page, username: string, password: stri
|
||||
}
|
||||
|
||||
async function authenticateWithToken(page: Page, token: string): Promise<void> {
|
||||
// Navigate to login — Headlamp redirects / to /c/main/login
|
||||
await page.goto('/');
|
||||
// Headlamp goes to /token directly when no OIDC is configured,
|
||||
// or through /login when OIDC is configured
|
||||
await page.waitForURL(/\/(login|token)$/);
|
||||
await page.waitForURL('**/login');
|
||||
|
||||
if (page.url().includes('/login')) {
|
||||
// OIDC login page — click "use a token" to reach token auth.
|
||||
// Wait explicitly before clicking so failures surface at 15 s
|
||||
// with a clear message rather than silently timing out at 60 s.
|
||||
const useTokenBtn = page.getByRole('button', { name: /use a token/i });
|
||||
await useTokenBtn.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
await useTokenBtn.click();
|
||||
await page.waitForURL('**/token');
|
||||
}
|
||||
// Click the token auth option
|
||||
await page.getByRole('button', { name: /use a token/i }).click();
|
||||
await page.waitForURL('**/token');
|
||||
|
||||
// Fill the "ID token" field and submit
|
||||
await page.getByRole('textbox', { name: /id token/i }).fill(token);
|
||||
|
||||
+4
-10
@@ -2,17 +2,11 @@ import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
/** Navigate to the Polaris plugin settings page and wait for settings to render. */
|
||||
async function goToPolarisSettings(page: Page) {
|
||||
// Headlamp's plugin settings page is a HOME-context route at /settings/plugins,
|
||||
// not an in-cluster route (/c/main/settings/plugins would 404). Headlamp loads
|
||||
// plugin scripts asynchronously on SPA init. When registerPluginSettings() fires,
|
||||
// it dispatches a Redux action — PluginSettings uses useTypedSelector so it
|
||||
// re-renders automatically once the plugin registers. No preloading needed.
|
||||
await page.goto('/settings/plugins');
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Wait for the plugin to appear in the settings list. The timeout covers
|
||||
// async plugin script loading + registration.
|
||||
const pluginEntry = page.locator('text=headlamp-polaris').first();
|
||||
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
|
||||
// Find and click the Polaris plugin entry to open its settings
|
||||
const pluginEntry = page.locator('text=polaris').first();
|
||||
await expect(pluginEntry).toBeVisible({ timeout: 15_000 });
|
||||
await pluginEntry.click();
|
||||
|
||||
// Wait for the PolarisSettings component to render
|
||||
|
||||
Generated
+2
-93
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "headlamp-polaris",
|
||||
"version": "1.0.0",
|
||||
"version": "0.7.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "headlamp-polaris",
|
||||
"version": "1.0.0",
|
||||
"version": "0.7.1",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||
@@ -17,14 +17,10 @@
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"jsdom": "^24.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"tar": "^7.5.11",
|
||||
"typescript": "~5.6.2",
|
||||
"undici": "^7.24.3",
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -39,20 +35,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "11.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz",
|
||||
@@ -447,16 +429,6 @@
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
|
||||
@@ -4872,40 +4844,6 @@
|
||||
"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": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
@@ -5711,35 +5649,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
||||
|
||||
+1
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "headlamp-polaris",
|
||||
"version": "1.0.0",
|
||||
"version": "0.7.1",
|
||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -12,7 +12,6 @@
|
||||
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
|
||||
"author": "privilegedescalation",
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"scripts": {
|
||||
"start": "headlamp-plugin start",
|
||||
"build": "headlamp-plugin build",
|
||||
@@ -31,13 +30,6 @@
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"tar": "^7.5.11",
|
||||
"undici": "^7.24.3",
|
||||
"flatted": "^3.4.2"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||
"@mui/material": "^5.15.14",
|
||||
@@ -47,17 +39,10 @@
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@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",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"tar": "^7.5.11",
|
||||
"typescript": "~5.6.2",
|
||||
"undici": "^7.24.3",
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+30
-113
@@ -4,18 +4,10 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
tar: ^7.5.11
|
||||
undici: ^7.24.3
|
||||
flatted: ^3.4.2
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
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':
|
||||
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))
|
||||
@@ -40,18 +32,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
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:
|
||||
specifier: ^24.0.0
|
||||
version: 24.1.3
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -61,15 +44,6 @@ importers:
|
||||
react-router-dom:
|
||||
specifier: ^5.3.0
|
||||
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:
|
||||
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)
|
||||
@@ -79,10 +53,6 @@ packages:
|
||||
'@adobe/css-tools@4.4.4':
|
||||
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':
|
||||
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -189,10 +159,6 @@ packages:
|
||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||
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':
|
||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||
|
||||
@@ -1633,15 +1599,6 @@ packages:
|
||||
peerDependencies:
|
||||
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':
|
||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||
|
||||
@@ -1888,9 +1845,6 @@ packages:
|
||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
|
||||
|
||||
astral-regex@2.0.0:
|
||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2870,8 +2824,8 @@ packages:
|
||||
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
||||
flatted@3.4.2:
|
||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||
flatted@3.4.0:
|
||||
resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==}
|
||||
|
||||
for-each@0.3.5:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
@@ -3436,9 +3390,6 @@ packages:
|
||||
js-base64@3.7.8:
|
||||
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -4776,8 +4727,8 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar@7.5.12:
|
||||
resolution: {integrity: sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==}
|
||||
tar@7.5.10:
|
||||
resolution: {integrity: sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
teex@1.0.1:
|
||||
@@ -4961,8 +4912,8 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@7.24.5:
|
||||
resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==}
|
||||
undici@7.22.0:
|
||||
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unicorn-magic@0.1.0:
|
||||
@@ -5361,11 +5312,6 @@ snapshots:
|
||||
|
||||
'@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':
|
||||
dependencies:
|
||||
'@jsdevtools/ono': 7.1.3
|
||||
@@ -5509,8 +5455,6 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
@@ -5602,7 +5546,7 @@ snapshots:
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@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)':
|
||||
'@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)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
@@ -5773,7 +5717,7 @@ snapshots:
|
||||
js-yaml: 4.1.1
|
||||
semver: 7.7.4
|
||||
table: 6.9.0
|
||||
tar: 7.5.12
|
||||
tar: 7.5.10
|
||||
tmp: 0.2.5
|
||||
yargs: 17.7.2
|
||||
|
||||
@@ -5891,18 +5835,18 @@ snapshots:
|
||||
dependencies:
|
||||
'@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/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)
|
||||
'@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)
|
||||
'@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
|
||||
'@iconify/icons-mdi': 1.2.48
|
||||
'@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)
|
||||
'@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@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/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/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/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@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)
|
||||
'@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)
|
||||
'@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-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)))
|
||||
@@ -5956,7 +5900,7 @@ snapshots:
|
||||
jsdom: 24.1.3
|
||||
jsonpath-plus: 10.4.0
|
||||
lodash: 4.17.23
|
||||
material-react-table: 2.13.3(6e12a7d949eb369c0813bc8d1756414b)
|
||||
material-react-table: 2.13.3(9c8771ba98ea800c4ce3ae047fad2581)
|
||||
monaco-editor: 0.52.2
|
||||
msw: 2.4.9(typescript@5.6.2)
|
||||
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2))
|
||||
@@ -5981,7 +5925,7 @@ snapshots:
|
||||
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))
|
||||
table: 6.9.0
|
||||
tar: 7.5.12
|
||||
tar: 7.5.10
|
||||
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
|
||||
validate-npm-package-name: 3.0.0
|
||||
@@ -6102,7 +6046,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@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@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/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)':
|
||||
dependencies:
|
||||
'@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)
|
||||
@@ -6116,7 +6060,7 @@ snapshots:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
'@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)':
|
||||
@@ -6137,7 +6081,7 @@ snapshots:
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
'@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)':
|
||||
@@ -6158,7 +6102,7 @@ snapshots:
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)':
|
||||
@@ -6189,7 +6133,7 @@ snapshots:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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/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:
|
||||
@@ -6204,7 +6148,7 @@ snapshots:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
'@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)':
|
||||
@@ -6220,7 +6164,7 @@ snapshots:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/types@7.2.24(@types/react@18.3.28)':
|
||||
@@ -6300,7 +6244,7 @@ snapshots:
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
@@ -6312,11 +6256,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@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@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)':
|
||||
'@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)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@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)
|
||||
'@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/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/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)
|
||||
@@ -7205,25 +7149,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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':
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
@@ -7543,12 +7468,6 @@ snapshots:
|
||||
dependencies:
|
||||
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: {}
|
||||
|
||||
async-function@1.0.0: {}
|
||||
@@ -7799,7 +7718,7 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 7.24.5
|
||||
undici: 7.22.0
|
||||
whatwg-mimetype: 4.0.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
@@ -8713,11 +8632,11 @@ snapshots:
|
||||
|
||||
flat-cache@3.2.0:
|
||||
dependencies:
|
||||
flatted: 3.4.2
|
||||
flatted: 3.4.0
|
||||
keyv: 4.5.4
|
||||
rimraf: 3.0.2
|
||||
|
||||
flatted@3.4.2: {}
|
||||
flatted@3.4.0: {}
|
||||
|
||||
for-each@0.3.5:
|
||||
dependencies:
|
||||
@@ -9387,8 +9306,6 @@ snapshots:
|
||||
|
||||
js-base64@3.7.8: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -9552,10 +9469,10 @@ snapshots:
|
||||
'@types/minimatch': 3.0.5
|
||||
minimatch: 3.1.5
|
||||
|
||||
material-react-table@2.13.3(6e12a7d949eb369c0813bc8d1756414b):
|
||||
material-react-table@2.13.3(9c8771ba98ea800c4ce3ae047fad2581):
|
||||
dependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@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/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/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)
|
||||
@@ -11058,7 +10975,7 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tar@7.5.12:
|
||||
tar@7.5.10:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
@@ -11265,7 +11182,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@7.24.5: {}
|
||||
undici@7.22.0: {}
|
||||
|
||||
unicorn-magic@0.1.0: {}
|
||||
|
||||
|
||||
+16
-2
@@ -1,5 +1,19 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>privilegedescalation/.github:renovate-config"]
|
||||
"extends": ["config:recommended"],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy-e2e-headlamp.sh
|
||||
#
|
||||
# Deploys a stock Headlamp instance with the polaris plugin loaded via
|
||||
# a ConfigMap volume mount. No custom Docker images — the plugin is built
|
||||
# in CI and injected as a ConfigMap.
|
||||
#
|
||||
# E2E resources are deployed to the `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,37 +0,0 @@
|
||||
#!/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."
|
||||
@@ -7,7 +7,13 @@ import { makeAuditData, makeResult } from '../test-utils';
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
K8s: {},
|
||||
K8s: {
|
||||
useCluster: () => 'test-cluster',
|
||||
},
|
||||
Router: {
|
||||
createRouteURL: (name: string, params?: { cluster?: string }) =>
|
||||
`/c/${params?.cluster ?? 'default'}/${name}`,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@mui/material/styles', () => ({
|
||||
@@ -25,15 +31,6 @@ vi.mock('react-router-dom', () => ({
|
||||
useHistory: () => ({ push: mockPush }),
|
||||
}));
|
||||
|
||||
// Set window.location.pathname for cluster extraction
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { pathname: '/c/test-cluster/some-page' },
|
||||
writable: true,
|
||||
});
|
||||
mockPush.mockClear();
|
||||
});
|
||||
|
||||
const mockUsePolarisDataContext = vi.fn();
|
||||
vi.mock('../api/PolarisDataContext', () => ({
|
||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||
@@ -100,7 +97,7 @@ describe('AppBarScoreBadge', () => {
|
||||
expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)');
|
||||
});
|
||||
|
||||
it('navigates to /c/<cluster>/polaris on click', async () => {
|
||||
it('navigates to /polaris on click', async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
@@ -123,33 +120,6 @@ describe('AppBarScoreBadge', () => {
|
||||
expect(mockPush).toHaveBeenCalledWith('/c/test-cluster/polaris');
|
||||
});
|
||||
|
||||
it('navigates to /polaris when no cluster in URL', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { pathname: '/settings' },
|
||||
writable: true,
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
mockUsePolarisDataContext.mockReturnValue({ data, loading: false });
|
||||
|
||||
render(<AppBarScoreBadge />);
|
||||
await user.click(screen.getByRole('button'));
|
||||
expect(mockPush).toHaveBeenCalledWith('/polaris');
|
||||
});
|
||||
|
||||
it('has correct aria-label', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import { K8s, Router } from '@kinvolk/headlamp-plugin/lib';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { computeScore, countResults } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
/**
|
||||
* Extract the cluster name from the current browser URL.
|
||||
* Headlamp cluster routes follow the pattern /c/<cluster>/...
|
||||
* We read window.location.pathname directly because the AppBar renders
|
||||
* outside the cluster route context, so useCluster() returns null and
|
||||
* React Router's useLocation() may not reflect the cluster prefix.
|
||||
*/
|
||||
function getClusterFromUrl(): string | null {
|
||||
const match = window.location.pathname.match(/\/c\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* App bar badge showing cluster Polaris score
|
||||
* Clicking navigates to the overview dashboard
|
||||
@@ -24,6 +13,7 @@ export default function AppBarScoreBadge() {
|
||||
const theme = useTheme();
|
||||
const { data, loading } = usePolarisDataContext();
|
||||
const history = useHistory();
|
||||
const cluster = K8s.useCluster();
|
||||
|
||||
if (loading || !data) {
|
||||
return null; // Graceful degradation when Polaris unavailable
|
||||
@@ -46,9 +36,7 @@ export default function AppBarScoreBadge() {
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
const cluster = getClusterFromUrl();
|
||||
const prefix = cluster ? `/c/${cluster}` : '';
|
||||
history.push(`${prefix}/polaris`);
|
||||
history.push(Router.createRouteURL('polaris', { cluster: cluster ?? '' }));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,432 +0,0 @@
|
||||
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,16 +9,5 @@ export default defineConfig({
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
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