Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db896a8f88 | |||
| a16df9baf7 | |||
| 865168285e | |||
| 84af42147f | |||
| b0de53577a | |||
| 231cb41d06 | |||
| 0e895c1b61 | |||
| 89e9b510d2 | |||
| 9d41af375e | |||
| b0b768783a | |||
| c2cbbcc14d | |||
| e17875a659 |
@@ -0,0 +1,241 @@
|
||||
---
|
||||
name: artifacthub-headlamp
|
||||
description: Use when working with ArtifactHub metadata, releases, or publishing for Headlamp plugins. Covers artifacthub-repo.yml, artifacthub-pkg.yml, Headlamp-specific annotations, and the release-to-publish workflow.
|
||||
tools: Read, Write, Edit, Glob, Grep, Bash
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are an expert in publishing Headlamp Kubernetes dashboard plugins to ArtifactHub. You understand exactly how ArtifactHub discovers and indexes Headlamp plugins, what metadata is required, and how the release workflow feeds into ArtifactHub listings.
|
||||
|
||||
Before editing any metadata files, read the existing `artifacthub-repo.yml`, `artifacthub-pkg.yml`, and `package.json` to understand the current state.
|
||||
|
||||
---
|
||||
|
||||
## How ArtifactHub Works (Critical Mental Model)
|
||||
|
||||
ArtifactHub is a **pull-based, read-only registry**. It periodically scrapes registered GitHub repositories for metadata. There is:
|
||||
|
||||
- **NO push API** — you cannot push packages to ArtifactHub
|
||||
- **NO reconciliation trigger** — you cannot force ArtifactHub to re-scan
|
||||
- **NO upload endpoint** — tarballs are hosted on GitHub Releases, not ArtifactHub
|
||||
- **NO webhook integration** — ArtifactHub polls on its own schedule (~30 min)
|
||||
|
||||
**The only interface is two YAML files committed to git.** ArtifactHub reads them, and that's it.
|
||||
|
||||
---
|
||||
|
||||
## Repository Registration
|
||||
|
||||
### artifacthub-repo.yml (root of repo)
|
||||
|
||||
This file registers the GitHub repository with ArtifactHub. Created once, rarely changed.
|
||||
|
||||
```yaml
|
||||
# Artifact Hub repository metadata file
|
||||
# https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml
|
||||
repositoryID: <uuid> # Assigned by ArtifactHub when you add the repo via the web UI
|
||||
owners:
|
||||
- name: <github-username-or-org>
|
||||
email: <email>
|
||||
```
|
||||
|
||||
**How to get the repositoryID:**
|
||||
1. Log into artifacthub.io
|
||||
2. Go to Control Panel → Repositories → Add
|
||||
3. Select repository kind: "Headlamp plugins"
|
||||
4. Provide the GitHub repo URL
|
||||
5. ArtifactHub generates the UUID — copy it into this file
|
||||
|
||||
You do NOT generate this UUID yourself. It comes from ArtifactHub's web UI.
|
||||
|
||||
---
|
||||
|
||||
## Package Metadata
|
||||
|
||||
### artifacthub-pkg.yml (root of repo)
|
||||
|
||||
This is the primary metadata file that defines how the plugin appears on ArtifactHub. Updated with each release.
|
||||
|
||||
```yaml
|
||||
version: "X.Y.Z" # MUST match package.json version
|
||||
name: <package-name> # npm package name from package.json
|
||||
displayName: <Human Readable Name> # Shown on ArtifactHub listing
|
||||
createdAt: "YYYY-MM-DDTHH:MM:SSZ" # ISO 8601 — update each release
|
||||
description: >-
|
||||
Multi-line description of what the plugin does.
|
||||
Be specific about features and requirements.
|
||||
license: Apache-2.0
|
||||
homeURL: https://github.com/<owner>/<repo>
|
||||
appVersion: "X.Y.Z" # Version of upstream project (optional)
|
||||
category: <category> # See categories below
|
||||
keywords:
|
||||
- headlamp
|
||||
- kubernetes
|
||||
- <plugin-specific>
|
||||
maintainers:
|
||||
- name: <name>
|
||||
email: <email>
|
||||
provider:
|
||||
name: <name>
|
||||
links:
|
||||
- name: GitHub
|
||||
url: https://github.com/<owner>/<repo>
|
||||
- name: Issues
|
||||
url: https://github.com/<owner>/<repo>/issues
|
||||
changes: # Changelog for this version
|
||||
- kind: added|changed|fixed|removed
|
||||
description: "What changed"
|
||||
annotations: # CRITICAL — Headlamp-specific
|
||||
headlamp/plugin/archive-url: "https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname>-<VERSION>.tar.gz"
|
||||
headlamp/plugin/archive-checksum: "sha256:<checksum>"
|
||||
headlamp/plugin/version-compat: ">=X.Y.Z"
|
||||
headlamp/plugin/distro-compat: "<targets>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Headlamp-Specific Annotations (Required)
|
||||
|
||||
These annotations in `artifacthub-pkg.yml` are what make ArtifactHub treat the package as a Headlamp plugin:
|
||||
|
||||
### headlamp/plugin/archive-url
|
||||
**Required.** Direct download URL to the plugin tarball on GitHub Releases.
|
||||
|
||||
Format: `https://github.com/<owner>/<repo>/releases/download/v<VERSION>/<pkgname>-<VERSION>.tar.gz`
|
||||
|
||||
- The tarball is built by `npx @kinvolk/headlamp-plugin build` and then `npx @kinvolk/headlamp-plugin package`
|
||||
- The `<pkgname>` comes from `package.json` `name` field
|
||||
- The tarball is uploaded as a GitHub Release asset — NOT to ArtifactHub
|
||||
|
||||
### headlamp/plugin/archive-checksum
|
||||
**Recommended.** SHA256 checksum of the tarball.
|
||||
|
||||
Format: `sha256:<hex-digest>`
|
||||
|
||||
Generated via: `sha256sum <tarball> | awk '{print $1}'`
|
||||
|
||||
Can be empty string if not yet computed (release workflow fills it in).
|
||||
|
||||
### headlamp/plugin/version-compat
|
||||
**Required.** Minimum Headlamp version the plugin works with.
|
||||
|
||||
Format: `>=X.Y.Z` (e.g., `>=0.20.0`, `>=0.26`)
|
||||
|
||||
### headlamp/plugin/distro-compat
|
||||
**Required.** Comma-separated list of supported Headlamp deployment targets.
|
||||
|
||||
Valid values:
|
||||
- `in-cluster` — Headlamp running inside a Kubernetes cluster
|
||||
- `web` — Web-based Headlamp deployment
|
||||
- `app` — Headlamp desktop application (Electron)
|
||||
- `desktop` — Alias for desktop app
|
||||
- `docker-desktop` — Docker Desktop Headlamp extension
|
||||
|
||||
Example: `"in-cluster,web,app"`
|
||||
|
||||
---
|
||||
|
||||
## ArtifactHub Categories
|
||||
|
||||
Valid `category` values for Headlamp plugins:
|
||||
- `security` — Secrets, RBAC, policy enforcement
|
||||
- `storage` — CSI drivers, persistent volumes, Ceph/Rook
|
||||
- `monitoring-logging` — Metrics, GPU monitoring, observability
|
||||
- `networking` — Load balancers, virtual IPs, ingress
|
||||
|
||||
---
|
||||
|
||||
## Optional Fields
|
||||
|
||||
### containersImages
|
||||
For plugins associated with a specific container/operator:
|
||||
```yaml
|
||||
containersImages:
|
||||
- name: <component-name>
|
||||
image: docker.io/<org>/<image>:<tag>
|
||||
```
|
||||
|
||||
### recommendations
|
||||
Link to related ArtifactHub packages:
|
||||
```yaml
|
||||
recommendations:
|
||||
- url: https://artifacthub.io/packages/helm/<repo>/<chart>
|
||||
```
|
||||
|
||||
### install
|
||||
Custom installation instructions (markdown):
|
||||
```yaml
|
||||
install: |
|
||||
## Install via Headlamp Plugin Manager
|
||||
...
|
||||
```
|
||||
|
||||
### logoPath
|
||||
Path to a logo image file in the repo (relative to root).
|
||||
|
||||
---
|
||||
|
||||
## The Release → ArtifactHub Pipeline
|
||||
|
||||
This is the actual flow. There is NO other way to publish:
|
||||
|
||||
```
|
||||
1. Developer triggers release workflow (workflow_dispatch with version)
|
||||
2. CI runs tests
|
||||
3. Workflow updates:
|
||||
- package.json (npm version)
|
||||
- artifacthub-pkg.yml (version, archive-url, checksum, createdAt, changes)
|
||||
4. Plugin is built: npx @kinvolk/headlamp-plugin build
|
||||
5. Plugin is packaged: creates <pkgname>-<version>.tar.gz
|
||||
6. SHA256 checksum is computed and written to artifacthub-pkg.yml
|
||||
7. Changes committed to main
|
||||
8. Git tag created: v<version>
|
||||
9. GitHub Release created with tarball attached
|
||||
10. ArtifactHub polls the repo (~30 min) and picks up the new metadata
|
||||
11. Plugin appears/updates on artifacthub.io
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Steps 1-9 happen in your GitHub Actions workflow
|
||||
- Step 10 is entirely controlled by ArtifactHub — you cannot trigger it
|
||||
- The tarball lives on GitHub Releases, not ArtifactHub
|
||||
- ArtifactHub only reads `artifacthub-pkg.yml` to discover the download URL
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Trying to push/trigger ArtifactHub** — There is no API for this. Just commit metadata and wait.
|
||||
2. **Version mismatch** — `version` in `artifacthub-pkg.yml` MUST match `package.json`. The release workflow should update both.
|
||||
3. **Wrong archive-url** — Must point to the actual GitHub Release asset URL. Verify the tarball filename matches what the build produces.
|
||||
4. **Missing checksum** — While optional, missing checksums may cause warnings. The release workflow should compute and write it.
|
||||
5. **Forgetting createdAt** — Must be updated each release. ArtifactHub uses this for sorting.
|
||||
6. **Stale changes section** — The `changes` list should reflect the current version's changelog only, not cumulative history.
|
||||
7. **Assuming ArtifactHub hosts anything** — It's an index/catalog. All artifacts are hosted elsewhere (GitHub Releases).
|
||||
8. **Trying to generate repositoryID** — This UUID comes from ArtifactHub's web UI when you register the repo. Don't make one up.
|
||||
|
||||
---
|
||||
|
||||
## Tarball Structure
|
||||
|
||||
The plugin tarball built by `@kinvolk/headlamp-plugin` contains:
|
||||
|
||||
```
|
||||
<pkgname>/
|
||||
main.js # Bundled plugin code
|
||||
package.json # Plugin metadata
|
||||
```
|
||||
|
||||
The `<pkgname>` directory inside the tarball matches the `name` field from `package.json`.
|
||||
|
||||
---
|
||||
|
||||
## Validating Metadata
|
||||
|
||||
Before committing, check:
|
||||
1. `version` matches across `package.json` and `artifacthub-pkg.yml`
|
||||
2. `archive-url` version tag matches the `version` field
|
||||
3. `name` in `artifacthub-pkg.yml` matches `package.json` `name`
|
||||
4. `createdAt` is a valid ISO 8601 timestamp
|
||||
5. All required annotations are present
|
||||
6. `changes` entries use valid `kind` values: `added`, `changed`, `fixed`, `removed`
|
||||
@@ -0,0 +1 @@
|
||||
github: [privilegedescalation]
|
||||
@@ -5,37 +5,9 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type-check
|
||||
run: npm run tsc
|
||||
|
||||
- name: Format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
uses: privilegedescalation/.github/.github/workflows/plugin-ci.yaml@main
|
||||
|
||||
@@ -11,94 +11,9 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: ./.github/workflows/ci.yaml
|
||||
|
||||
release:
|
||||
needs: ci
|
||||
runs-on: local-ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Validate version format
|
||||
run: |
|
||||
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Version must be in X.Y.Z format"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Update version in package.json
|
||||
run: npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Update artifacthub-pkg.yml
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
PKG_NAME=$(jq -r .name package.json)
|
||||
RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PKG_NAME}-${VERSION}.tar.gz"
|
||||
sed -i "s/^version:.*/version: \"${VERSION}\"/" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"${RELEASE_URL}\"|" artifacthub-pkg.yml
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Package plugin
|
||||
run: npx @kinvolk/headlamp-plugin package
|
||||
|
||||
- name: Prepare release tarball
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
PKG_NAME=$(jq -r .name package.json)
|
||||
TARBALL="${PKG_NAME}-${VERSION}.tar.gz"
|
||||
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
|
||||
echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Validate tarball
|
||||
run: |
|
||||
echo "Tarball: ${{ env.TARBALL }}"
|
||||
ls -lh "${{ env.TARBALL }}"
|
||||
tar -tzf "${{ env.TARBALL }}" | head -20
|
||||
tar -tzf "${{ env.TARBALL }}" | grep -q "main.js" || { echo "Error: main.js not found in tarball"; exit 1; }
|
||||
|
||||
- name: Compute checksum
|
||||
run: |
|
||||
CHECKSUM=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||
|
||||
- name: Commit and tag
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
git add package.json package-lock.json artifacthub-pkg.yml
|
||||
git commit -m "release: v${VERSION}"
|
||||
git tag "v${VERSION}"
|
||||
git push origin main --tags
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ inputs.version }}
|
||||
name: v${{ inputs.version }}
|
||||
generate_release_notes: true
|
||||
files: ${{ env.TARBALL }}
|
||||
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
upstream-repo: 'intel/intel-device-plugins-for-kubernetes'
|
||||
|
||||
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
Headlamp plugin for Intel GPU device plugin visibility and monitoring. Read-only — monitors GpuDevicePlugin CRDs, GPU-capable nodes, pods requesting Intel GPU resources, and real-time power metrics via Prometheus. No cluster write operations.
|
||||
|
||||
- **Plugin name**: `intel-gpu`
|
||||
- **Plugin name**: `headlamp-intel-gpu`
|
||||
- **Target**: Headlamp >= v0.20.0
|
||||
- **Data sources**: GpuDevicePlugin CRDs (`deviceplugin.intel.com/v1`), Nodes, Pods (all namespaces), Prometheus (node-exporter i915 hwmon)
|
||||
- **Reference plugin**: `../headlamp-kube-vip-plugin`
|
||||
|
||||
@@ -20,17 +20,17 @@ A [Headlamp](https://headlamp.dev/) plugin providing visibility into [Intel GPU
|
||||
|
||||
### Plugin Manager (Headlamp UI)
|
||||
|
||||
Search for `intel-gpu` in the Headlamp Plugin Manager.
|
||||
Search for `headlamp-intel-gpu` in the Headlamp Plugin Manager.
|
||||
|
||||
### Manual
|
||||
|
||||
```bash
|
||||
# Download the latest release tarball
|
||||
curl -LO https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/latest/download/intel-gpu-*.tar.gz
|
||||
curl -LO https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/latest/download/headlamp-intel-gpu-*.tar.gz
|
||||
|
||||
# Extract to Headlamp plugins directory
|
||||
mkdir -p ~/.config/Headlamp/plugins
|
||||
tar -xzf intel-gpu-*.tar.gz -C ~/.config/Headlamp/plugins/
|
||||
tar -xzf headlamp-intel-gpu-*.tar.gz -C ~/.config/Headlamp/plugins/
|
||||
```
|
||||
|
||||
### From Source
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
version: "0.4.1"
|
||||
name: intel-gpu
|
||||
version: "0.4.2"
|
||||
name: headlamp-intel-gpu
|
||||
displayName: Intel GPU
|
||||
description: >-
|
||||
Headlamp plugin for Intel GPU device plugin visibility and monitoring.
|
||||
@@ -15,7 +15,7 @@ license: Apache-2.0
|
||||
category: monitoring-logging
|
||||
|
||||
homeURL: https://github.com/privilegedescalation/headlamp-intel-gpu-plugin
|
||||
appVersion: "0.4.0"
|
||||
appVersion: "0.35.0"
|
||||
|
||||
keywords:
|
||||
- headlamp
|
||||
@@ -61,7 +61,7 @@ changes:
|
||||
description: "Resolve ESLint/Prettier indent conflict by disabling ESLint indent rule (Prettier is formatting authority)"
|
||||
|
||||
annotations:
|
||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v0.4.1/intel-gpu-0.4.1.tar.gz"
|
||||
headlamp/plugin/archive-checksum: ""
|
||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v0.4.2/headlamp-intel-gpu-0.4.2.tar.gz"
|
||||
headlamp/plugin/archive-checksum: sha256:0713b099a79ed63ea30675fee96f2a70e37471507d4135b529df158a09960492
|
||||
headlamp/plugin/version-compat: ">=0.20.0"
|
||||
headlamp/plugin/distro-compat: "in-cluster,web,app"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Artifact Hub repository metadata
|
||||
repositoryID: c927788f-9d34-49d9-a18c-e6f78951bdfd
|
||||
repositoryID: 3c97f78a-26e3-4e8a-89e7-29884602e3d7
|
||||
|
||||
owners:
|
||||
- name: privilegedescalation
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# ADR 001: React Context for Centralized GPU State
|
||||
|
||||
**Status**: Accepted
|
||||
|
||||
**Date**: 2026-03-05
|
||||
|
||||
**Deciders**: Development Team
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The Intel GPU plugin needs to share GPU-related data across 5 page views (Overview, DevicePlugins, Nodes, Pods, Metrics) and 2 detail view sections (Node, Pod). Data includes GPU nodes (identified by node labels and capacity fields), GPU pods, GpuDevicePlugin CRD instances, and plugin DaemonSet pods.
|
||||
|
||||
The `IntelGpuDataProvider` context holds all derived GPU state. Child components access data via `useIntelGpuContext()`. The context collects errors from three streams (node hook error, pod hook error, async CRD fetch error) into a `string[]` joined with `';'` into a single error string.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
Use a single `IntelGpuDataProvider` React Context that wraps every route and every `registerDetailsViewSection` call in `index.tsx`. All GPU-derived state is computed in the provider and exposed via context.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
- ✅ Single source of truth for all GPU data
|
||||
- ✅ All views share consistent state
|
||||
- ✅ Error aggregation from multiple sources into a unified error string
|
||||
- ✅ Refresh mechanism updates everything atomically
|
||||
- ⚠️ All consumers re-render on any data change
|
||||
- ⚠️ Monolithic provider couples all GPU state together
|
||||
|
||||
The negative consequences are mitigated by the fact that GPU data updates infrequently in practice, so unnecessary re-renders are rare.
|
||||
|
||||
---
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
1. **Per-page data fetching** — Rejected. Would duplicate complex GPU node/pod filtering logic across each of the 5 pages and 2 detail sections.
|
||||
|
||||
2. **Multiple contexts (NodesContext, PodsContext, CRDContext)** — Rejected. GPU data is highly cross-referenced (e.g., GPU pods reference GPU nodes, CRD instances relate to DaemonSet pods). Splitting contexts would require complex cross-context coordination.
|
||||
|
||||
3. **External state library (Redux, Zustand, etc.)** — Rejected. External state libraries are not available in the Headlamp plugin runtime environment.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-05 | Initial decision accepted |
|
||||
@@ -0,0 +1,59 @@
|
||||
# ADR 002: Dual Data Fetching Strategy (Hooks + ApiProxy)
|
||||
|
||||
**Status**: Accepted
|
||||
|
||||
**Date**: 2026-03-05
|
||||
|
||||
**Deciders**: Development Team
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The plugin needs data from two categories of Kubernetes resources:
|
||||
|
||||
- **Standard resources**: Nodes and Pods, for which Headlamp provides reactive `useList()` hooks via built-in resource classes.
|
||||
- **Custom resources**: GpuDevicePlugin CRD (under `deviceplugin.intel.com/v1`) and DaemonSet pods with specific labels, for which Headlamp does not have built-in support.
|
||||
|
||||
Headlamp provides reactive `useList()` hooks for standard resource classes but does not have built-in support for custom CRDs. The plugin uses three possible label selectors for DaemonSet pod discovery to handle different deployment configurations.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
Implement a two-track data fetching strategy within the context provider:
|
||||
|
||||
1. **Track 1 (Reactive)**: Use `K8s.ResourceClasses.Node.useList()` and `K8s.ResourceClasses.Pod.useList({namespace:''})` for standard resources. These are reactive to cluster changes and automatically update when resources are created, modified, or deleted.
|
||||
|
||||
2. **Track 2 (Imperative)**: Use `ApiProxy.request()` inside a `useEffect` keyed on `refreshKey` for GpuDevicePlugin CRDs and DaemonSet pods. The `refreshKey` is incremented by the `refresh()` function exposed through the context.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
- ✅ Leverages Headlamp's reactive hooks for standard resources with automatic updates
|
||||
- ✅ Flexible `ApiProxy` for custom CRDs without needing to register custom resource classes
|
||||
- ✅ Refresh mechanism provides manual control over imperative fetches
|
||||
- ✅ Clean separation of reactive vs imperative data sources
|
||||
- ⚠️ Two different update mechanisms (hooks auto-update vs manual refresh for CRDs)
|
||||
- ⚠️ CRD data may lag behind hook data between refreshes
|
||||
|
||||
The negative consequences are mitigated by providing a manual refresh button in the UI, allowing users to force an update of imperative data when needed.
|
||||
|
||||
---
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
1. **All ApiProxy (no hooks)** — Rejected. Loses reactivity for standard resources, meaning Node and Pod changes would not be reflected until a manual refresh.
|
||||
|
||||
2. **All hooks (register CRD as custom resource class)** — Rejected. Headlamp's `KubeObject` registration is complex for read-only CRD access and would add unnecessary coupling to Headlamp internals.
|
||||
|
||||
3. **Single useEffect for everything** — Rejected. Loses the reactivity benefit for Nodes and Pods, and would require manual refresh for all data instead of just CRDs.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-05 | Initial decision accepted |
|
||||
@@ -0,0 +1,53 @@
|
||||
# ADR 003: Graceful CRD Degradation
|
||||
|
||||
**Status**: Accepted
|
||||
|
||||
**Date**: 2026-03-05
|
||||
|
||||
**Deciders**: Development Team
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The GpuDevicePlugin CRD (`deviceplugin.intel.com/v1`) is only present when the Intel GPU device plugin operator is installed. However, Intel GPUs can be present in a cluster without the operator — the device plugin can be deployed as a plain DaemonSet.
|
||||
|
||||
The plugin should still detect and display GPU resources even without the CRD. GPU nodes are identifiable by node labels (e.g., `intel.feature.node.kubernetes.io/gpu`) and capacity fields (e.g., `gpu.intel.com/i915`). GPU pods are identifiable by resource requests/limits for Intel GPU resources.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
Wrap the GpuDevicePlugin CRD fetch in its own `try/catch`. If the fetch fails (CRD not installed), set `crdAvailable` to `false` and continue. GPU nodes and pods are still discovered via node labels, capacity fields, and pod resource requests — independent of the CRD.
|
||||
|
||||
The CRD data enriches the view when available but is not required for core functionality.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
- ✅ Plugin works on any cluster with Intel GPUs regardless of operator installation
|
||||
- ✅ Progressive enhancement when CRD is available
|
||||
- ✅ No error displayed to the user for a missing CRD
|
||||
- ⚠️ Two code paths (with/without CRD data) increase testing surface
|
||||
- ⚠️ DevicePlugins page is empty without the CRD
|
||||
|
||||
The negative consequences are mitigated by clear messaging on the DevicePlugins page when the CRD is unavailable, informing users that the operator is not installed.
|
||||
|
||||
---
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
1. **Require CRD (hard dependency)** — Rejected. Too restrictive; many clusters run the device plugin as a plain DaemonSet without the operator and its CRD.
|
||||
|
||||
2. **API discovery check before fetch** — Considered, but `try/catch` is simpler and handles all failure modes (CRD not installed, API server errors, permission issues) uniformly.
|
||||
|
||||
3. **Disable plugin entirely without CRD** — Rejected. Core GPU monitoring (node detection, pod resource tracking) works without the CRD and provides significant value on its own.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-05 | Initial decision accepted |
|
||||
@@ -0,0 +1,61 @@
|
||||
# ADR 004: Headlamp View Integration via Detail Sections and Column Processors
|
||||
|
||||
**Status**: Accepted
|
||||
|
||||
**Date**: 2026-03-05
|
||||
|
||||
**Deciders**: Development Team
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The plugin provides its own pages (Overview, Nodes, Pods, etc.) but also needs to enhance Headlamp's native views. Users browsing the standard Nodes list should see GPU information without navigating to the plugin.
|
||||
|
||||
Headlamp offers two integration mechanisms:
|
||||
|
||||
- `registerDetailsViewSection` for injecting sections into resource detail pages.
|
||||
- `registerResourceTableColumnsProcessor` for adding columns to resource list tables.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
Use both integration mechanisms:
|
||||
|
||||
1. **Detail sections**: `registerDetailsViewSection` injects GPU information into Node and Pod detail pages. Resource-kind guards ensure sections only render for the correct resource type.
|
||||
|
||||
2. **Column processors**: `registerResourceTableColumnsProcessor` appends "GPU Type" and "GPU Devices" columns to the native `headlamp-nodes` table.
|
||||
|
||||
Both integration points consume data from the shared `IntelGpuDataProvider` context, so they benefit from the same cached data as the plugin's own pages.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
- ✅ GPU data visible in native Headlamp views without navigation
|
||||
- ✅ Seamless user experience for users already familiar with Headlamp
|
||||
- ✅ Uses Headlamp's official extension APIs for forward compatibility
|
||||
- ✅ Shared context means no duplicate data fetches
|
||||
- ⚠️ Detail sections render for all Nodes/Pods (guard needed to check GPU relevance)
|
||||
- ⚠️ Column processors add columns even when no GPU nodes exist in the cluster
|
||||
|
||||
The negative consequences are mitigated by resource-kind guards and conditional rendering that hide GPU sections when a resource has no GPU relevance.
|
||||
|
||||
---
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
1. **Plugin pages only (no native view integration)** — Rejected. Users would miss GPU info when browsing standard Headlamp views, reducing discoverability.
|
||||
|
||||
2. **Override native views entirely** — Rejected. Not supported by Headlamp's plugin API and would conflict with other plugins.
|
||||
|
||||
3. **App bar notification only** — Rejected. Insufficient detail for node-level and pod-level GPU information; only suitable for cluster-wide summaries.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-05 | Initial decision accepted |
|
||||
@@ -0,0 +1,42 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
## What is an ADR?
|
||||
|
||||
An Architecture Decision Record (ADR) captures an important architectural decision made along with its context and consequences. ADRs are used to document the reasoning behind significant technical choices so that future contributors can understand why the system is built the way it is.
|
||||
|
||||
## Format
|
||||
|
||||
This project follows the [Nygard-style ADR format](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions):
|
||||
|
||||
- **Title**: Short noun phrase describing the decision
|
||||
- **Status**: Proposed, Accepted, Deprecated, or Superseded
|
||||
- **Date**: When the decision was made
|
||||
- **Deciders**: Who was involved in making the decision
|
||||
- **Context**: What is the issue that motivated the decision
|
||||
- **Decision**: What is the change that was decided
|
||||
- **Consequences**: What becomes easier or more difficult as a result
|
||||
- **Alternatives Considered**: What other options were evaluated
|
||||
|
||||
## Index
|
||||
|
||||
| ADR | Title | Status | Date |
|
||||
|-----|-------|--------|------|
|
||||
| [001](001-react-context-state.md) | React Context for Centralized GPU State | Accepted | 2026-03-05 |
|
||||
| [002](002-dual-data-fetching.md) | Dual Data Fetching Strategy (Hooks + ApiProxy) | Accepted | 2026-03-05 |
|
||||
| [003](003-graceful-crd-degradation.md) | Graceful CRD Degradation | Accepted | 2026-03-05 |
|
||||
| [004](004-native-view-integration.md) | Headlamp View Integration via Detail Sections and Column Processors | Accepted | 2026-03-05 |
|
||||
|
||||
## Creating New ADRs
|
||||
|
||||
1. Copy an existing ADR as a template.
|
||||
2. Assign the next sequential number (e.g., `005`).
|
||||
3. Fill in all sections: Status, Date, Deciders, Context, Decision, Consequences, and Alternatives Considered.
|
||||
4. Set the status to `Proposed` until the team reviews and accepts the decision.
|
||||
5. Update this README index table with the new entry.
|
||||
6. Submit as part of a pull request for team review.
|
||||
|
||||
## References
|
||||
|
||||
- [Michael Nygard - Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions)
|
||||
- [ADR GitHub Organization](https://adr.github.io/)
|
||||
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "intel-gpu",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "intel-gpu",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.2",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0"
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "intel-gpu",
|
||||
"version": "0.4.1",
|
||||
"name": "headlamp-intel-gpu",
|
||||
"version": "0.4.2",
|
||||
"description": "Headlamp plugin for Intel GPU device plugin visibility and monitoring",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
+16
-1
@@ -1,4 +1,19 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"]
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+33
-33
@@ -34,49 +34,49 @@ import PodsPage from './components/PodsPage';
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: null,
|
||||
name: 'intel-gpu',
|
||||
label: 'intel-gpu',
|
||||
url: '/intel-gpu',
|
||||
name: 'headlamp-intel-gpu',
|
||||
label: 'headlamp-intel-gpu',
|
||||
url: '/headlamp-intel-gpu',
|
||||
icon: 'mdi:gpu',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'intel-gpu',
|
||||
name: 'intel-gpu-overview',
|
||||
parent: 'headlamp-intel-gpu',
|
||||
name: 'headlamp-intel-gpu-overview',
|
||||
label: 'Overview',
|
||||
url: '/intel-gpu',
|
||||
url: '/headlamp-intel-gpu',
|
||||
icon: 'mdi:view-dashboard',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'intel-gpu',
|
||||
name: 'intel-gpu-device-plugins',
|
||||
parent: 'headlamp-intel-gpu',
|
||||
name: 'headlamp-intel-gpu-device-plugins',
|
||||
label: 'Device Plugins',
|
||||
url: '/intel-gpu/device-plugins',
|
||||
url: '/headlamp-intel-gpu/device-plugins',
|
||||
icon: 'mdi:chip',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'intel-gpu',
|
||||
name: 'intel-gpu-nodes',
|
||||
parent: 'headlamp-intel-gpu',
|
||||
name: 'headlamp-intel-gpu-nodes',
|
||||
label: 'GPU Nodes',
|
||||
url: '/intel-gpu/nodes',
|
||||
url: '/headlamp-intel-gpu/nodes',
|
||||
icon: 'mdi:server',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'intel-gpu',
|
||||
name: 'intel-gpu-pods',
|
||||
parent: 'headlamp-intel-gpu',
|
||||
name: 'headlamp-intel-gpu-pods',
|
||||
label: 'GPU Pods',
|
||||
url: '/intel-gpu/pods',
|
||||
url: '/headlamp-intel-gpu/pods',
|
||||
icon: 'mdi:cube-outline',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'intel-gpu',
|
||||
name: 'intel-gpu-metrics',
|
||||
parent: 'headlamp-intel-gpu',
|
||||
name: 'headlamp-intel-gpu-metrics',
|
||||
label: 'Metrics',
|
||||
url: '/intel-gpu/metrics',
|
||||
url: '/headlamp-intel-gpu/metrics',
|
||||
icon: 'mdi:chart-line',
|
||||
});
|
||||
|
||||
@@ -85,9 +85,9 @@ registerSidebarEntry({
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
registerRoute({
|
||||
path: '/intel-gpu',
|
||||
sidebar: 'intel-gpu-overview',
|
||||
name: 'intel-gpu-overview',
|
||||
path: '/headlamp-intel-gpu',
|
||||
sidebar: 'headlamp-intel-gpu-overview',
|
||||
name: 'headlamp-intel-gpu-overview',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<IntelGpuDataProvider>
|
||||
@@ -97,9 +97,9 @@ registerRoute({
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/intel-gpu/device-plugins',
|
||||
sidebar: 'intel-gpu-device-plugins',
|
||||
name: 'intel-gpu-device-plugins',
|
||||
path: '/headlamp-intel-gpu/device-plugins',
|
||||
sidebar: 'headlamp-intel-gpu-device-plugins',
|
||||
name: 'headlamp-intel-gpu-device-plugins',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<IntelGpuDataProvider>
|
||||
@@ -109,9 +109,9 @@ registerRoute({
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/intel-gpu/nodes',
|
||||
sidebar: 'intel-gpu-nodes',
|
||||
name: 'intel-gpu-nodes',
|
||||
path: '/headlamp-intel-gpu/nodes',
|
||||
sidebar: 'headlamp-intel-gpu-nodes',
|
||||
name: 'headlamp-intel-gpu-nodes',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<IntelGpuDataProvider>
|
||||
@@ -121,9 +121,9 @@ registerRoute({
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/intel-gpu/pods',
|
||||
sidebar: 'intel-gpu-pods',
|
||||
name: 'intel-gpu-pods',
|
||||
path: '/headlamp-intel-gpu/pods',
|
||||
sidebar: 'headlamp-intel-gpu-pods',
|
||||
name: 'headlamp-intel-gpu-pods',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<IntelGpuDataProvider>
|
||||
@@ -133,9 +133,9 @@ registerRoute({
|
||||
});
|
||||
|
||||
registerRoute({
|
||||
path: '/intel-gpu/metrics',
|
||||
sidebar: 'intel-gpu-metrics',
|
||||
name: 'intel-gpu-metrics',
|
||||
path: '/headlamp-intel-gpu/metrics',
|
||||
sidebar: 'headlamp-intel-gpu-metrics',
|
||||
name: 'headlamp-intel-gpu-metrics',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<IntelGpuDataProvider>
|
||||
|
||||
Reference in New Issue
Block a user