e2e: shared volume plugin deployment for CI tests #59
@@ -48,9 +48,14 @@ Polaris must be deployed in the `polaris` namespace with the dashboard component
|
|||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
### Option 1: Headlamp Plugin Manager (Recommended)
|
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Install via the Headlamp UI:
|
||||||
|
|
||||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp via Helm:
|
1. Go to **Settings → Plugins**
|
||||||
|
2. Click **Catalog** tab
|
||||||
|
3. Search for "Polaris"
|
||||||
|
4. Click **Install**
|
||||||
|
|
||||||
|
Or configure Headlamp via Helm:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
config:
|
config:
|
||||||
@@ -62,56 +67,6 @@ pluginsManager:
|
|||||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
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
|
## 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:
|
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:
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
version: "0.7.0"
|
version: "0.7.1"
|
||||||
name: headlamp-polaris
|
name: headlamp-polaris
|
||||||
displayName: Polaris
|
displayName: Polaris
|
||||||
createdAt: "2026-02-05T19:00:00Z"
|
createdAt: "2026-02-05T19:00:00Z"
|
||||||
@@ -28,7 +28,7 @@ maintainers:
|
|||||||
- name: privilegedescalation
|
- name: privilegedescalation
|
||||||
email: "chris@farhood.org"
|
email: "chris@farhood.org"
|
||||||
annotations:
|
annotations:
|
||||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.0/headlamp-polaris-0.7.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/version-compat: ">=0.26"
|
||||||
headlamp/plugin/archive-checksum: sha256:69b7adf74416d72580981387b1861e2b5fcf7f96ae9f0620f85f623f8b6dab56
|
headlamp/plugin/archive-checksum: sha256:5ea27f3beeab9730a13a752a0a7c2270eca83dcbbe813a6cabeb6a22fa9d5174
|
||||||
headlamp/plugin/distro-compat: in-cluster
|
headlamp/plugin/distro-compat: in-cluster
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import { makeAuditData, makeResult } from '../test-utils';
|
|||||||
// Mock Headlamp lib
|
// Mock Headlamp lib
|
||||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||||
ApiProxy: { request: vi.fn() },
|
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', () => ({
|
vi.mock('@mui/material/styles', () => ({
|
||||||
@@ -31,6 +25,15 @@ vi.mock('react-router-dom', () => ({
|
|||||||
useHistory: () => ({ push: mockPush }),
|
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();
|
const mockUsePolarisDataContext = vi.fn();
|
||||||
vi.mock('../api/PolarisDataContext', () => ({
|
vi.mock('../api/PolarisDataContext', () => ({
|
||||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||||
@@ -97,7 +100,7 @@ describe('AppBarScoreBadge', () => {
|
|||||||
expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)');
|
expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('navigates to /polaris on click', async () => {
|
it('navigates to /c/<cluster>/polaris on click', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const data = makeAuditData([
|
const data = makeAuditData([
|
||||||
makeResult({
|
makeResult({
|
||||||
@@ -120,6 +123,33 @@ describe('AppBarScoreBadge', () => {
|
|||||||
expect(mockPush).toHaveBeenCalledWith('/c/test-cluster/polaris');
|
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', () => {
|
it('has correct aria-label', () => {
|
||||||
const data = makeAuditData([
|
const data = makeAuditData([
|
||||||
makeResult({
|
makeResult({
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
import { K8s, Router } from '@kinvolk/headlamp-plugin/lib';
|
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { computeScore, countResults } from '../api/polaris';
|
import { computeScore, countResults } from '../api/polaris';
|
||||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
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
|
* App bar badge showing cluster Polaris score
|
||||||
* Clicking navigates to the overview dashboard
|
* Clicking navigates to the overview dashboard
|
||||||
@@ -13,7 +24,6 @@ export default function AppBarScoreBadge() {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { data, loading } = usePolarisDataContext();
|
const { data, loading } = usePolarisDataContext();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const cluster = K8s.useCluster();
|
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return null; // Graceful degradation when Polaris unavailable
|
return null; // Graceful degradation when Polaris unavailable
|
||||||
@@ -36,7 +46,9 @@ export default function AppBarScoreBadge() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
history.push(Router.createRouteURL('polaris', { cluster: cluster ?? '' }));
|
const cluster = getClusterFromUrl();
|
||||||
|
const prefix = cluster ? `/c/${cluster}` : '';
|
||||||
|
history.push(`${prefix}/polaris`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+1
-1
@@ -99,7 +99,7 @@ registerRoute({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Register plugin settings
|
// Register plugin settings
|
||||||
registerPluginSettings('headlamp-polaris', PolarisSettings, true);
|
registerPluginSettings('polaris', PolarisSettings, true);
|
||||||
|
|
||||||
// Register details view section for supported controller types
|
// Register details view section for supported controller types
|
||||||
registerDetailsViewSection(({ resource }) => {
|
registerDetailsViewSection(({ resource }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user