Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dashboard-provisioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"clickstack": minor
---

feat: add dashboard provisioning via k8s-sidecar that discovers labeled ConfigMaps across namespaces. Requires hyperdxio/hyperdx#1962 (file-based dashboard provisioner).
18 changes: 18 additions & 0 deletions charts/clickstack/templates/hyperdx/dashboard-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{- if and .Values.hyperdx.dashboards.enabled .Values.hyperdx.dashboards.configMaps }}
{{- /*
Inline dashboard ConfigMap; labeled for discovery by the dashboard provisioner
alongside any external dashboard ConfigMaps from application charts.
*/ -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "clickstack.fullname" . }}-dashboards
labels:
{{- include "clickstack.labels" . | nindent 4 }}
hyperdx.io/dashboard: "true"
data:
{{- range $key, $value := .Values.hyperdx.dashboards.configMaps }}
{{ $key }}: |
{{- $value | nindent 4 }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{{- if .Values.hyperdx.dashboards.enabled }}
{{- /*
RBAC for the dashboard provisioner.
ClusterRole so it can discover labeled dashboard ConfigMaps across all namespaces,
allowing application teams to manage dashboards from their own namespaces.
*/ -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "clickstack.fullname" . }}-dashboard-provisioner
labels:
{{- include "clickstack.labels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "clickstack.fullname" . }}-dashboard-provisioner
labels:
{{- include "clickstack.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["list", "get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "clickstack.fullname" . }}-dashboard-provisioner
labels:
{{- include "clickstack.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "clickstack.fullname" . }}-dashboard-provisioner
subjects:
- kind: ServiceAccount
name: {{ include "clickstack.fullname" . }}-dashboard-provisioner
namespace: {{ .Release.Namespace }}
{{- end }}
49 changes: 49 additions & 0 deletions charts/clickstack/templates/hyperdx/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ spec:
imagePullSecrets:
{{- toYaml .Values.global.imagePullSecrets | nindent 8 }}
{{- end }}
{{- if .Values.hyperdx.dashboards.enabled }}
serviceAccountName: {{ include "clickstack.fullname" . }}-dashboard-provisioner
{{- end }}
{{- if .Values.mongodb.enabled }}
initContainers:
- name: wait-for-mongodb
Expand Down Expand Up @@ -118,6 +121,52 @@ spec:
value: {{ tpl .Values.hyperdx.deployment.defaultSources . | quote }}
{{- end }}
{{- end }}
{{- if .Values.hyperdx.dashboards.enabled }}
- name: DASHBOARD_PROVISIONER_DIR
value: "/dashboards"
- name: DASHBOARD_PROVISIONER_INTERVAL
value: {{ mul .Values.hyperdx.dashboards.syncInterval 1000 | quote }}
- name: DASHBOARD_PROVISIONER_ALL_TEAMS
value: "true"
{{- end }}
{{- with .Values.hyperdx.deployment.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.hyperdx.dashboards.enabled }}
volumeMounts:
- name: dashboards
mountPath: /dashboards
readOnly: true
{{- end }}
{{- if .Values.hyperdx.dashboards.enabled }}
- name: dashboard-watcher
image: {{ .Values.hyperdx.dashboards.sidecarImage }}
resources:
limits:
cpu: 50m
memory: 64Mi
requests:
cpu: 10m
memory: 32Mi
env:
- name: LABEL
value: "hyperdx.io/dashboard"
- name: LABEL_VALUE
value: "true"
- name: FOLDER
value: "/dashboards"
- name: RESOURCE
value: "configmap"
- name: NAMESPACE
value: "ALL"
- name: UNIQUE_FILENAMES
value: "true"
volumeMounts:
- name: dashboards
mountPath: /dashboards
{{- end }}
{{- if .Values.hyperdx.dashboards.enabled }}
volumes:
- name: dashboards
emptyDir: {}
{{- end }}
219 changes: 219 additions & 0 deletions charts/clickstack/tests/dashboard-provisioner_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
suite: Test Dashboard Provisioner
templates:
- hyperdx/deployment.yaml
- hyperdx/dashboard-configmap.yaml
- hyperdx/dashboard-provisioner-rbac.yaml
tests:
- it: should not render RBAC or ConfigMap when dashboards are disabled
set:
hyperdx:
dashboards:
enabled: false
asserts:
- hasDocuments:
count: 0
template: hyperdx/dashboard-configmap.yaml
- hasDocuments:
count: 0
template: hyperdx/dashboard-provisioner-rbac.yaml

- it: should not add sidecar when dashboards are disabled
set:
hyperdx:
dashboards:
enabled: false
asserts:
- lengthEqual:
path: spec.template.spec.containers
count: 1
template: hyperdx/deployment.yaml

- it: should add dashboard-watcher sidecar when enabled
set:
hyperdx:
dashboards:
enabled: true
asserts:
- lengthEqual:
path: spec.template.spec.containers
count: 2
template: hyperdx/deployment.yaml
- equal:
path: spec.template.spec.containers[1].name
value: dashboard-watcher
template: hyperdx/deployment.yaml

- it: should use the k8s-sidecar image for watcher
set:
hyperdx:
dashboards:
enabled: true
sidecarImage: "kiwigrid/k8s-sidecar:2.5.0"
asserts:
- equal:
path: spec.template.spec.containers[1].image
value: "kiwigrid/k8s-sidecar:2.5.0"
template: hyperdx/deployment.yaml

- it: should configure watcher to discover labeled ConfigMaps across all namespaces
set:
hyperdx:
dashboards:
enabled: true
asserts:
- contains:
path: spec.template.spec.containers[1].env
content:
name: LABEL
value: "hyperdx.io/dashboard"
template: hyperdx/deployment.yaml
- contains:
path: spec.template.spec.containers[1].env
content:
name: NAMESPACE
value: "ALL"
template: hyperdx/deployment.yaml
- contains:
path: spec.template.spec.containers[1].env
content:
name: UNIQUE_FILENAMES
value: "true"
template: hyperdx/deployment.yaml

- it: should set DASHBOARD_PROVISIONER_DIR on the app container
set:
hyperdx:
dashboards:
enabled: true
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: DASHBOARD_PROVISIONER_DIR
value: "/dashboards"
template: hyperdx/deployment.yaml

- it: should set DASHBOARD_PROVISIONER_INTERVAL in milliseconds
set:
hyperdx:
dashboards:
enabled: true
syncInterval: 60
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: DASHBOARD_PROVISIONER_INTERVAL
value: "60000"
template: hyperdx/deployment.yaml

- it: should share a dashboards volume between app and watcher
set:
hyperdx:
dashboards:
enabled: true
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: dashboards
emptyDir: {}
template: hyperdx/deployment.yaml
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: dashboards
mountPath: /dashboards
readOnly: true
template: hyperdx/deployment.yaml
- contains:
path: spec.template.spec.containers[1].volumeMounts
content:
name: dashboards
mountPath: /dashboards
template: hyperdx/deployment.yaml

- it: should use the dashboard-provisioner service account
set:
hyperdx:
dashboards:
enabled: true
asserts:
- equal:
path: spec.template.spec.serviceAccountName
value: RELEASE-NAME-clickstack-dashboard-provisioner
template: hyperdx/deployment.yaml

- it: should create ServiceAccount, ClusterRole, and ClusterRoleBinding
set:
hyperdx:
dashboards:
enabled: true
asserts:
- isKind:
of: ServiceAccount
documentIndex: 0
template: hyperdx/dashboard-provisioner-rbac.yaml
- isKind:
of: ClusterRole
documentIndex: 1
template: hyperdx/dashboard-provisioner-rbac.yaml
- isKind:
of: ClusterRoleBinding
documentIndex: 2
template: hyperdx/dashboard-provisioner-rbac.yaml

- it: should grant cluster-wide configmap list, get, and watch permissions
set:
hyperdx:
dashboards:
enabled: true
asserts:
- equal:
path: rules[0].verbs
value: ["list", "get", "watch"]
documentIndex: 1
template: hyperdx/dashboard-provisioner-rbac.yaml

- it: should not render inline ConfigMap when configMaps is empty
set:
hyperdx:
dashboards:
enabled: true
configMaps: {}
asserts:
- hasDocuments:
count: 0
template: hyperdx/dashboard-configmap.yaml

- it: should render inline ConfigMap with discovery label
set:
hyperdx:
dashboards:
enabled: true
configMaps:
test.json: |
{ "name": "Test", "tiles": [] }
asserts:
- equal:
path: metadata.labels["hyperdx.io/dashboard"]
value: "true"
template: hyperdx/dashboard-configmap.yaml

- it: should include multiple dashboard files in inline ConfigMap
set:
hyperdx:
dashboards:
enabled: true
configMaps:
k8s-overview.json: |
{ "name": "Kubernetes Overview", "tiles": [] }
app-metrics.json: |
{ "name": "App Metrics", "tiles": [] }
asserts:
- isNotNull:
path: data["k8s-overview.json"]
template: hyperdx/dashboard-configmap.yaml
- isNotNull:
path: data["app-metrics.json"]
template: hyperdx/dashboard-configmap.yaml
27 changes: 27 additions & 0 deletions charts/clickstack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ hyperdx:
app: 3000
opamp: 4320

# ── Dashboard provisioning ────────────────────────────────
# Discovers and upserts dashboard JSON into MongoDB (matched by name, never deletes)
#
# Two ways to provide dashboards:
# 1. Inline: set configMaps below with dashboard JSON
# 2. External: any ConfigMap in the cluster with the label "hyperdx.io/dashboard: true"
# will be discovered automatically (ideal for application charts managing their own dashboards)
dashboards:
enabled: false
# Image for the k8s-sidecar that watches for labeled ConfigMaps
sidecarImage: "kiwigrid/k8s-sidecar:2.5.0"
# Seconds between MongoDB upsert cycles
syncInterval: 30
# Inline dashboard definitions - key is filename, value is exported dashboard JSON
configMaps: {}
# Example:
# configMaps:
# k8s-overview.json: |
# { "name": "Kubernetes Overview", "tiles": [...] }
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

# ── K8s ConfigMap (clickstack-config) ────────────────────
# Shared non-sensitive environment variables. Used by HyperDX and OTEL collector via envFrom.
# All values support Helm template expressions (rendered via tpl).
Expand Down