Cluster Inventory development
Headlamp Cluster Inventory support is alpha/experimental and disabled by
default. The upstream Cluster Inventory API is currently v1alpha1 and the
Headlamp integration uses the v0.1.x API, so fields and behavior may change.
Headlamp can discover additional clusters from Cluster Inventory API
ClusterProfile resources when started with Cluster Inventory enabled. The
backend uses sigs.k8s.io/cluster-inventory-api v0.1.0, the pkg/access
provider configuration package, and ClusterProfile.status.accessProviders.
The provider configuration file is not a ClusterProfile status object. It uses
the upstream access configuration shape with a top-level providers array:
{
"providers": [
{
"name": "static-token-spoke-a",
"execConfig": {
"apiVersion": "client.authentication.k8s.io/v1",
"command": "/tmp/headlamp-ci/static-token-exec.sh",
"provideClusterInfo": true
}
}
]
}
Start the backend explicitly while testing:
npm run backend:build
KUBECONFIG="$WORK/hub.kubeconfig" \
HEADLAMP_BACKEND_TOKEN=headlamp \
HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true \
./backend/headlamp-server -dev -listen-addr=localhost \
--enable-cluster-inventory \
--cluster-inventory-provider-file "$WORK/provider-config.json" \
--cluster-inventory-label-selector='!headlamp.dev/ignore' \
--cluster-inventory-root-reconcile-interval=10s \
--cluster-inventory-no-crd-cache-ttl=30s
In another terminal:
npm run frontend:start
Install the v0.1.0 CRD on clusters that publish inventory:
kubectl --context kind-ci-hub apply -f \
https://raw.githubusercontent.com/kubernetes-sigs/cluster-inventory-api/v0.1.0/config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml
Patch sample status with status.accessProviders and health conditions:
ClusterProfile.spec.clusterManager.name is required by the v0.1.0 CRD, even
when the access details are patched later through the status subresource. The
CRD also requires reason on each condition, so include it even when adapting
examples that omit the field.
kubectl --context kind-ci-hub -n inventory-e2e apply -f - <<'EOF'
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ClusterProfile
metadata:
name: spoke-a
spec:
clusterManager:
name: headlamp-local-e2e
EOF
kubectl --context kind-ci-hub -n inventory-e2e patch clusterprofiles spoke-a \
--subresource=status --type=merge \
-p "$(jq -n --arg server "$SPOKE_A_SERVER" --arg ca "$SPOKE_A_CA" '{
status: {
conditions: [{
type: "ControlPlaneHealthy",
status: "True",
reason: "HealthCheckSucceeded",
message: "control plane endpoint is ready",
lastTransitionTime: "2026-05-10T00:00:00Z"
}],
accessProviders: [{
name: "static-token-spoke-a",
cluster: {
server: $server,
"certificate-authority-data": $ca
}
}]
}
}')"
To hide a ClusterProfile from Headlamp, add the ignore label. The default
Helm chart selector is !headlamp.dev/ignore, so profiles with that label are
not watched or converted into Headlamp contexts:
kubectl --context kind-ci-hub -n inventory-e2e label clusterprofile spoke-a \
headlamp.dev/ignore=true
Run the focused web E2E only after the local topology is running:
cd e2e-tests
HEADLAMP_CLUSTER_INVENTORY_E2E=true \
HEADLAMP_TEST_URL=http://localhost:3000 \
npx playwright test -g "Cluster Inventory"
Before cleanup, verify that setup artifacts stayed outside tracked paths:
git status --short