Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1fa087011 | |||
| bfe926546b | |||
| dccc393857 | |||
| f414dafa28 | |||
| 6e914ad71f | |||
| d923e655fe | |||
| a1ef628fb5 | |||
| b8129a0dbb | |||
| 7facb9be10 | |||
| 1b86407d8b | |||
| 40df014b6b | |||
| b217a8119e | |||
| 818f4bc9cb |
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Check if release is already finalized
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
TARBALL_URL="https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz"
|
||||
TARBALL_URL="https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz"
|
||||
HTTP_CODE=$(curl -sL -o /tmp/release.tar.gz -w "%{http_code}" "$TARBALL_URL" 2>/dev/null)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
ACTUAL="sha256:$(sha256sum /tmp/release.tar.gz | awk '{print $1}')"
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: |
|
||||
[ "$SKIP_BUILD" = "true" ] && exit 0
|
||||
GH_API="https://api.github.com/repos/cpfarhood/polaris-headlamp-plugin"
|
||||
GH_API="https://api.github.com/repos/cpfarhood/headlamp-polaris-plugin"
|
||||
# Create release or fetch existing one
|
||||
BODY=$(curl -s -X POST \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${{ secrets.GH_PAT }}" \
|
||||
-H "Content-Type: application/gzip" \
|
||||
"https://uploads.github.com/repos/cpfarhood/polaris-headlamp-plugin/releases/${RELEASE_ID}/assets?name=${TARBALL}" \
|
||||
"https://uploads.github.com/repos/cpfarhood/headlamp-polaris-plugin/releases/${RELEASE_ID}/assets?name=${TARBALL}" \
|
||||
--data-binary "@${TARBALL}"
|
||||
echo "GitHub release updated with same tarball"
|
||||
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
git checkout main
|
||||
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/polaris-headlamp-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
|
||||
sed -i "s|^version:.*|version: ${VERSION}|" artifacthub-pkg.yml
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions[bot]@git.farh.net"
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
git tag -f ${GITHUB_REF_NAME}
|
||||
git push -f origin ${GITHUB_REF_NAME}
|
||||
# Also push to GitHub directly to avoid waiting for mirror sync
|
||||
git remote add github https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/polaris-headlamp-plugin.git 2>/dev/null || true
|
||||
git remote add github https://x-access-token:${{ secrets.GH_PAT }}@github.com/cpfarhood/headlamp-polaris-plugin.git 2>/dev/null || true
|
||||
git push github main 2>/dev/null || true
|
||||
git push -f github ${GITHUB_REF_NAME} 2>/dev/null || true
|
||||
echo "Tag ${GITHUB_REF_NAME} aligned with updated metadata"
|
||||
|
||||
@@ -40,14 +40,14 @@ Headlamp will fetch and install the plugin on startup.
|
||||
|
||||
### Option 2: Docker init container
|
||||
|
||||
The plugin ships as a container image at `git.farh.net/farhoodliquor/polaris-headlamp-plugin`.
|
||||
The plugin ships as a container image at `git.farh.net/farhoodliquor/headlamp-polaris-plugin`.
|
||||
|
||||
Add it as an init container in your Headlamp Helm values:
|
||||
|
||||
```yaml
|
||||
initContainers:
|
||||
- name: polaris-plugin
|
||||
image: git.farh.net/farhoodliquor/polaris-headlamp-plugin:v0.0.1
|
||||
image: git.farh.net/farhoodliquor/headlamp-polaris-plugin:v0.0.1
|
||||
command: ["sh", "-c", "cp -r /plugins/* /headlamp/plugins/"]
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
@@ -64,7 +64,7 @@ volumeMounts:
|
||||
|
||||
### Option 3: Manual tarball install
|
||||
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/cpfarhood/polaris-headlamp-plugin/releases) or the [Gitea releases page](https://git.farh.net/farhoodliquor/polaris-headlamp-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/cpfarhood/headlamp-polaris-plugin/releases) or the [Gitea releases page](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
|
||||
```bash
|
||||
tar xzf polaris-headlamp-plugin-0.0.1.tar.gz -C /headlamp/plugins/
|
||||
@@ -112,7 +112,7 @@ subjects:
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/cpfarhood/polaris-headlamp-plugin.git
|
||||
git clone https://github.com/cpfarhood/headlamp-polaris-plugin.git
|
||||
cd polaris-headlamp-plugin
|
||||
npm install
|
||||
```
|
||||
@@ -193,7 +193,7 @@ This triggers two CI pipelines:
|
||||
**Gitea Actions** (`.gitea/workflows/release.yaml`):
|
||||
1. Build the plugin in a `node:20` container
|
||||
2. Package a `.tar.gz` tarball
|
||||
3. Build and push a Docker image to `git.farh.net/farhoodliquor/polaris-headlamp-plugin:{tag}` and `:latest`
|
||||
3. Build and push a Docker image to `git.farh.net/farhoodliquor/headlamp-polaris-plugin:{tag}` and `:latest`
|
||||
4. Create a Gitea release with the tarball attached
|
||||
|
||||
**GitHub Actions** (`.github/workflows/release.yml`):
|
||||
@@ -220,8 +220,8 @@ When releasing a new version, update `artifacthub-pkg.yml`:
|
||||
## Links
|
||||
|
||||
- [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris-headlamp-plugin/polaris-headlamp-plugin)
|
||||
- [GitHub (mirror)](https://github.com/cpfarhood/polaris-headlamp-plugin)
|
||||
- [Gitea (source of truth)](https://git.farh.net/farhoodliquor/polaris-headlamp-plugin)
|
||||
- [GitHub (mirror)](https://github.com/cpfarhood/headlamp-polaris-plugin)
|
||||
- [Gitea (source of truth)](https://git.farh.net/farhoodliquor/headlamp-polaris-plugin)
|
||||
- [Headlamp](https://headlamp.dev/)
|
||||
- [Fairwinds Polaris](https://polaris.docs.fairwinds.com/)
|
||||
|
||||
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
version: 0.0.6
|
||||
name: polaris-headlamp-plugin
|
||||
version: 0.0.8
|
||||
name: headlamp-polaris-plugin
|
||||
displayName: Polaris
|
||||
createdAt: "2026-02-05T19:00:00Z"
|
||||
description: Surfaces Fairwinds Polaris audit results inside the Headlamp UI.
|
||||
license: MIT
|
||||
homeURL: "https://github.com/cpfarhood/polaris-headlamp-plugin"
|
||||
homeURL: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
||||
category: security
|
||||
keywords:
|
||||
- polaris
|
||||
@@ -15,14 +15,14 @@ keywords:
|
||||
- kubernetes
|
||||
links:
|
||||
- name: Source
|
||||
url: "https://github.com/cpfarhood/polaris-headlamp-plugin"
|
||||
url: "https://github.com/cpfarhood/headlamp-polaris-plugin"
|
||||
- name: Polaris
|
||||
url: "https://polaris.docs.fairwinds.com/"
|
||||
maintainers:
|
||||
- name: cpfarhood
|
||||
email: "chris@farhood.org"
|
||||
annotations:
|
||||
headlamp/plugin/archive-url: "https://github.com/cpfarhood/polaris-headlamp-plugin/releases/download/v0.0.6/polaris-headlamp-plugin-0.0.6.tar.gz"
|
||||
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.0.8/polaris-headlamp-plugin-0.0.8.tar.gz"
|
||||
headlamp/plugin/version-compat: ">=0.26"
|
||||
headlamp/plugin/archive-checksum: sha256:12e72b6a64e3f1c73f542b6328d56391c2cc2906a9a9d7eff58fbf27f14a8680
|
||||
headlamp/plugin/archive-checksum: sha256:425082de35c4c59363c8cfe5859145781f76dc2423084bd8b4585b28bec66784
|
||||
headlamp/plugin/distro-compat: in-cluster
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
repositoryID: fb4c3789-de2b-4667-8fff-34f22e5648da
|
||||
repositoryID: fc3397f6-a75a-4950-ab50-da75c08a8089
|
||||
owners:
|
||||
- name: cpfarhood
|
||||
email: "chris@farhood.org"
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "polaris-headlamp-plugin",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "polaris-headlamp-plugin",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.8",
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "polaris-headlamp-plugin",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||
"scripts": {
|
||||
"start": "headlamp-plugin start",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { AuditData, getRefreshInterval, usePolarisData } from './polaris';
|
||||
|
||||
interface PolarisDataContextValue {
|
||||
data: AuditData | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const PolarisDataContext = React.createContext<PolarisDataContextValue | null>(null);
|
||||
|
||||
export function PolarisDataProvider(props: { children: React.ReactNode }) {
|
||||
const interval = getRefreshInterval();
|
||||
const state = usePolarisData(interval);
|
||||
|
||||
return <PolarisDataContext.Provider value={state}>{props.children}</PolarisDataContext.Provider>;
|
||||
}
|
||||
|
||||
export function usePolarisDataContext(): PolarisDataContextValue {
|
||||
const ctx = React.useContext(PolarisDataContext);
|
||||
if (ctx === null) {
|
||||
throw new Error('usePolarisDataContext must be used within a PolarisDataProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
+22
-2
@@ -77,9 +77,9 @@ function countResultSet(rs: ResultSet, counts: ResultCounts): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function countResults(data: AuditData): ResultCounts {
|
||||
function countResultItems(results: Result[]): ResultCounts {
|
||||
const counts: ResultCounts = { total: 0, pass: 0, warning: 0, danger: 0 };
|
||||
for (const result of data.Results) {
|
||||
for (const result of results) {
|
||||
countResultSet(result.Results, counts);
|
||||
if (result.PodResult) {
|
||||
countResultSet(result.PodResult.Results, counts);
|
||||
@@ -91,6 +91,26 @@ export function countResults(data: AuditData): ResultCounts {
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function countResults(data: AuditData): ResultCounts {
|
||||
return countResultItems(data.Results);
|
||||
}
|
||||
|
||||
export function countResultsForItems(results: Result[]): ResultCounts {
|
||||
return countResultItems(results);
|
||||
}
|
||||
|
||||
export function getNamespaces(data: AuditData): string[] {
|
||||
const namespaces = new Set<string>();
|
||||
for (const result of data.Results) {
|
||||
namespaces.add(result.Namespace);
|
||||
}
|
||||
return Array.from(namespaces).sort();
|
||||
}
|
||||
|
||||
export function filterResultsByNamespace(data: AuditData, namespace: string): Result[] {
|
||||
return data.Results.filter(r => r.Namespace === namespace);
|
||||
}
|
||||
|
||||
// --- Settings ---
|
||||
|
||||
export const INTERVAL_OPTIONS = [
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
|
||||
import React from 'react';
|
||||
import { getNamespaces } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
const registeredNamespaces = new Set<string>();
|
||||
|
||||
export default function DynamicSidebarRegistrar() {
|
||||
const { data } = usePolarisDataContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!data) return;
|
||||
|
||||
const namespaces = getNamespaces(data);
|
||||
for (const ns of namespaces) {
|
||||
if (registeredNamespaces.has(ns)) continue;
|
||||
registeredNamespaces.add(ns);
|
||||
registerSidebarEntry({
|
||||
parent: 'polaris',
|
||||
name: `polaris-ns-${ns}`,
|
||||
label: ns,
|
||||
url: `/polaris/${ns}`,
|
||||
icon: 'mdi:folder-outline',
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
Loader,
|
||||
NameValueTable,
|
||||
SectionBox,
|
||||
SectionHeader,
|
||||
SimpleTable,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import {
|
||||
computeScore,
|
||||
countResultsForItems,
|
||||
filterResultsByNamespace,
|
||||
Result,
|
||||
ResultCounts,
|
||||
} from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
function scoreStatus(score: number): 'success' | 'warning' | 'error' {
|
||||
if (score >= 80) return 'success';
|
||||
if (score >= 50) return 'warning';
|
||||
return 'error';
|
||||
}
|
||||
|
||||
function resourceCounts(result: Result): ResultCounts {
|
||||
return countResultsForItems([result]);
|
||||
}
|
||||
|
||||
export default function NamespaceDetailView() {
|
||||
const { namespace } = useParams<{ namespace: string }>();
|
||||
const { data, loading, error } = usePolarisDataContext();
|
||||
|
||||
if (loading) {
|
||||
return <Loader title={`Loading Polaris data for ${namespace}...`} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={`Polaris — ${namespace}`} />
|
||||
<SectionBox title="Error">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Status',
|
||||
value: <StatusLabel status="error">{error}</StatusLabel>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={`Polaris — ${namespace}`} />
|
||||
<SectionBox title="No Data">
|
||||
<NameValueTable rows={[{ name: 'Status', value: 'No Polaris audit results found.' }]} />
|
||||
</SectionBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const results = filterResultsByNamespace(data, namespace);
|
||||
const counts = countResultsForItems(results);
|
||||
const score = computeScore(counts);
|
||||
const status = scoreStatus(score);
|
||||
|
||||
const countsPerResource = new Map<string, ResultCounts>();
|
||||
for (const r of results) {
|
||||
countsPerResource.set(`${r.Namespace}/${r.Kind}/${r.Name}`, resourceCounts(r));
|
||||
}
|
||||
|
||||
function getResourceCounts(row: Result): ResultCounts {
|
||||
return countsPerResource.get(`${row.Namespace}/${row.Kind}/${row.Name}`) ?? resourceCounts(row);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={`Polaris — ${namespace}`} />
|
||||
|
||||
<SectionBox title="Namespace Score">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{
|
||||
name: 'Score',
|
||||
value: <StatusLabel status={status}>{score}%</StatusLabel>,
|
||||
},
|
||||
{ name: 'Total Checks', value: String(counts.total) },
|
||||
{
|
||||
name: 'Pass',
|
||||
value: <StatusLabel status="success">{counts.pass}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Warning',
|
||||
value: <StatusLabel status="warning">{counts.warning}</StatusLabel>,
|
||||
},
|
||||
{
|
||||
name: 'Danger',
|
||||
value: <StatusLabel status="error">{counts.danger}</StatusLabel>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
<SectionBox title="Resources">
|
||||
<SimpleTable
|
||||
columns={[
|
||||
{ label: 'Name', getter: (row: Result) => row.Name },
|
||||
{ label: 'Kind', getter: (row: Result) => row.Kind },
|
||||
{
|
||||
label: 'Pass',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="success">{getResourceCounts(row).pass}</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Warning',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="warning">{getResourceCounts(row).warning}</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Danger',
|
||||
getter: (row: Result) => (
|
||||
<StatusLabel status="error">{getResourceCounts(row).danger}</StatusLabel>
|
||||
),
|
||||
},
|
||||
]}
|
||||
data={results}
|
||||
emptyMessage={`No resources found in namespace "${namespace}".`}
|
||||
/>
|
||||
</SectionBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,14 +6,8 @@ import {
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import {
|
||||
AuditData,
|
||||
computeScore,
|
||||
countResults,
|
||||
getRefreshInterval,
|
||||
ResultCounts,
|
||||
usePolarisData,
|
||||
} from '../api/polaris';
|
||||
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
function scoreStatus(score: number): 'success' | 'warning' | 'error' {
|
||||
if (score >= 80) return 'success';
|
||||
@@ -71,8 +65,7 @@ function OverviewSection(props: { data: AuditData; counts: ResultCounts }) {
|
||||
}
|
||||
|
||||
export default function PolarisView() {
|
||||
const interval = getRefreshInterval();
|
||||
const { data, loading, error } = usePolarisData(interval);
|
||||
const { data, loading, error } = usePolarisDataContext();
|
||||
|
||||
if (loading) {
|
||||
return <Loader title="Loading Polaris audit data..." />;
|
||||
|
||||
+22
-1
@@ -4,6 +4,9 @@ import {
|
||||
registerSidebarEntry,
|
||||
} from '@kinvolk/headlamp-plugin/lib';
|
||||
import React from 'react';
|
||||
import { PolarisDataProvider } from './api/PolarisDataContext';
|
||||
import DynamicSidebarRegistrar from './components/DynamicSidebarRegistrar';
|
||||
import NamespaceDetailView from './components/NamespaceDetailView';
|
||||
import PolarisSettings from './components/PolarisSettings';
|
||||
import PolarisView from './components/PolarisView';
|
||||
|
||||
@@ -20,7 +23,25 @@ registerRoute({
|
||||
sidebar: 'polaris',
|
||||
name: 'polaris',
|
||||
exact: true,
|
||||
component: () => <PolarisView />,
|
||||
component: () => (
|
||||
<PolarisDataProvider>
|
||||
<DynamicSidebarRegistrar />
|
||||
<PolarisView />
|
||||
</PolarisDataProvider>
|
||||
),
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/polaris/:namespace',
|
||||
sidebar: 'polaris',
|
||||
name: 'polaris-namespace',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<PolarisDataProvider>
|
||||
<DynamicSidebarRegistrar />
|
||||
<NamespaceDetailView />
|
||||
</PolarisDataProvider>
|
||||
),
|
||||
});
|
||||
|
||||
registerPluginSettings('polaris-headlamp-plugin', PolarisSettings, true);
|
||||
|
||||
Reference in New Issue
Block a user