feat(monitoring): ship Grafana dashboards as pod-scoped ConfigMaps - #467
feat(monitoring): ship Grafana dashboards as pod-scoped ConfigMaps#467christopherfickess wants to merge 21 commits into
Conversation
…sRule Add an optional `spec.monitoring` block to the Mattermost CRD that lets the operator create observability resources, each behind its own flag: - `spec.monitoring.serviceMonitor.enabled` — Prometheus Operator ServiceMonitor targeting the existing `metrics` service port (/metrics:8067), with configurable scrape interval and extra labels (so a cluster's Prometheus serviceMonitorSelector can match it). - `spec.monitoring.grafanaDashboard.enabled` — ConfigMap holding the operator's embedded Grafana dashboard JSON, labelled `grafana_dashboard: "1"` (label/value overridable) for discovery by the Grafana dashboard sidecar. - `spec.monitoring.prometheusRule.enabled` — Prometheus Operator PrometheusRule holding embedded alerting/recording rules, with extra labels for ruleSelector matching. Ships a conservative starter rule set; defaults off because alert thresholds encode opinions that need per-environment tuning. Design notes: - Access direction is observability -> Mattermost only: all resources are created in the Mattermost namespace and consumed by an external stack (Prometheus scrapes/loads, Grafana's sidecar reads). The operator never writes into the monitoring namespace. - The Prometheus Operator CRDs are an optional cluster dependency. ServiceMonitor and PrometheusRule are NOT added to the controller's Owns() watch (which would fail startup when the CRDs are absent); creation degrades to a logged no-op when the kind is missing. GC still works via owner references. - Dashboards and rules are embedded via //go:embed from pkg/mattermost/dashboards/ and pkg/mattermost/prometheusrules/. Ships placeholders now; real content is a drop-in (one dashboard ConfigMap key / merged rule group per file). Pins prometheus-operator apis at v0.83.0 to match the existing k8s 0.33 / controller-runtime 0.21 stack (no transitive bump). Regenerates deepcopy + CRD; adds servicemonitors + prometheusrules to the (hand-maintained) operator ClusterRole. Unit tests cover all three generators. Co-Authored-By: Claude <noreply@anthropic.com>
…oards Reshape the monitoring POC into the "rules + ServiceMonitor" variant for the SRE review, removing the Grafana dashboard pieces (they land on a separate branch so the two can be compared side by side). - PrometheusRule: replace the single starter alert with a curated, Mattermost-focused set (server-down, crash-loop, CPU/mem vs limits, DB replica lag, HTTP 5xx rate, login-failure spike). Expressions are templated per CR via __NAMESPACE__/__SERVICE__/__POD_SELECTOR__ so alerts scope to this installation's pods by name/label — never a static IP or CIDR. - Add spec.monitoring.clientMetrics: explicit control over client/RUM + notification metrics (MM_METRICSSETTINGS_ENABLECLIENTMETRICS / ...ENABLENOTIFICATIONMETRICS). These default on in the server and ride the same /metrics endpoint, so no extra scrape target. - Add spec.monitoring.callsMetrics: an optional second ServiceMonitor targeting a separately-deployed rtcd Service (default :8045/metrics). rtcd is not managed by the operator, so the rtcd Service selector is supplied in the CR. - License warning: emit a MonitoringRequiresEnterpriseLicense event + log when monitoring is enabled but spec.licenseSecret is empty (the /metrics endpoint is Enterprise-gated). Wire an EventRecorder into the reconciler. - Remove the Grafana dashboard type/field, reconcile path, ConfigMap builder, placeholder dashboard, CreateConfigMapIfNotExists helper, and the Owns(ConfigMap) watch added for it. Regenerates deepcopy + CRD; promotes sigs.k8s.io/yaml to a direct dependency. Unit tests cover the rtcd ServiceMonitor port handling and rule pod-scoping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… flags Address review gaps on the rules+ServiceMonitor variant: #1 Cleanup on disable — each capability now deletes the resources it created when its flag is off (mirrors the Ingress DeleteIngress pattern): the metrics Service + ServiceMonitor, the rtcd ServiceMonitor, and the PrometheusRule. Adds DeleteServiceMonitor/DeletePrometheusRule (graceful when the Prometheus Operator CRDs are absent). The whole path is skipped when no `monitoring` block is declared, so non-users pay no per-reconcile cost. #2 Dedicated metrics Service — the ServiceMonitor now targets a new internal headless Service `<name>-metrics` (port 8067) instead of the app Service. This makes scraping work in every service mode, including useServiceLoadBalancer (where the app Service drops port 8067). Metrics stay internal — never exposed through the LoadBalancer. #3 clientMetrics.enabled is now *bool — an absent/empty block leaves the server defaults untouched instead of silently disabling client + notification metrics. #4 rtcd ServiceMonitor discovery labels — add callsMetrics.labels, falling back to serviceMonitor.labels, so the rtcd ServiceMonitor is actually selected by the cluster Prometheus even when only callsMetrics is configured. #6 Validation — CEL XValidation requires rtcdServiceSelector when callsMetrics is enabled; the rule pod selector is tightened to "<name>-[^-]+-[^-]+" so a name prefix (mm) no longer cross-matches a sibling install (mm-test). Regenerates deepcopy + CRD. Unit tests cover the metrics Service, the tightened selector, and the rtcd label fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt on the fixed rules+ServiceMonitor base. Adds the Grafana dashboard layer
so the two variants differ only by this feature (SRE review).
- spec.monitoring.grafanaDashboard (Enabled + sidecar discovery label/value),
reconcile path, and the Owns(ConfigMap) watch.
- All nine dashboards embedded from mattermost-performance-assets, transformed by
hack/transform_dashboards.py: datasource normalization (${DS_*} -> one
`datasource` variable, drop __inputs/__requires) and instance -> pod for the
four server dashboards (123 `instance=~"$server"` filters rewritten to
`pod=~"$server"`, scoped by a new `namespace` variable).
- One ConfigMap per dashboard (largest ~115KB) to stay under the 1MB limit and
match the sidecar convention.
- Cleanup: each dashboard ConfigMap carries a stable marker label; disabling the
flag deletes every owned dashboard ConfigMap, and reconcile prunes ones no
longer in the embedded set (rename/removal) — independent of the configurable
discovery label.
Regenerates deepcopy + CRD. Unit test asserts one ConfigMap per dashboard, the
discovery + prune labels, and the size ceiling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fast, Docker-free validation using envtest (real apiserver+etcd from binaries): - test/envtest/cel_validation_test.go — proves the CRD CEL rule: callsMetrics enabled without rtcdServiceSelector is rejected by the apiserver; valid and disabled variants are accepted. - controllers/.../monitoring_envtest_test.go (build tag `envtest`) — drives the monitoring reconcile: the dedicated metrics Service and the dashboard ConfigMaps are created when enabled and deleted when disabled; ServiceMonitor/ PrometheusRule degrade to a graceful skip when the Prometheus Operator CRDs are absent (scheme registered as in main.go). - `make test-envtest` fetches the apiserver binaries and runs both. Confirms gaps #1 (cleanup-on-disable), #2 (metrics Service works without the app Service's metrics port), and #6 (CEL validation) against a real apiserver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerate zz_generated.openapi.go so the CI make-generate diff is clean (the monitoring additions had only been run through controller-gen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds opt-in Mattermost monitoring resources and Grafana dashboard provisioning.
Changes:
- Adds ServiceMonitor, PrometheusRule, client/calls metrics, and Grafana dashboard APIs.
- Embeds and transforms nine dashboards into sidecar-discoverable ConfigMaps.
- Adds reconciliation, cleanup, RBAC, documentation, and envtest coverage.
Reviewed changes
Copilot reviewed 24 out of 30 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
Makefile |
Adds envtest target |
go.mod |
Adds monitoring dependencies |
go.sum |
Updates dependency checksums |
main.go |
Registers monitoring types |
apis/mattermost/v1beta1/mattermost_types.go |
Defines monitoring API |
apis/mattermost/v1beta1/zz_generated.deepcopy.go |
Adds generated deep copies |
config/crd/bases/installation.mattermost.com_mattermosts.yaml |
Publishes monitoring schema |
config/rbac/role.yaml |
Grants monitoring-resource access |
controllers/mattermost/mattermost/controller.go |
Adds recorder and ConfigMap watch |
controllers/mattermost/mattermost/mattermost.go |
Invokes monitoring reconciliation |
controllers/mattermost/mattermost/monitoring.go |
Reconciles monitoring resources |
controllers/mattermost/mattermost/monitoring_envtest_test.go |
Tests create/delete lifecycle |
docs/examples/mattermost_monitoring.yaml |
Documents monitoring configuration |
hack/transform_dashboards.py |
Transforms upstream dashboards |
pkg/mattermost/mattermost_v1beta.go |
Configures client metrics |
pkg/mattermost/monitoring.go |
Generates monitoring resources |
pkg/mattermost/monitoring_test.go |
Tests resource generation |
pkg/mattermost/prometheusrules/mattermost.yaml |
Defines alerting rules |
pkg/resources/create_resources.go |
Adds monitoring CRUD helpers |
test/envtest/cel_validation_test.go |
Tests CEL validation |
pkg/mattermost/dashboards/mattermost-calls-performance-monitoring.json |
Adds Calls dashboard |
pkg/mattermost/dashboards/mattermost-collapsed_reply_threads_performance.json |
Adds thread-performance dashboard |
pkg/mattermost/dashboards/mattermost-desktop-app-metrics.json |
Adds desktop dashboard |
pkg/mattermost/dashboards/mattermost-mobile-performance.json |
Adds mobile dashboard |
pkg/mattermost/dashboards/mattermost-notification-metrics.json |
Adds notification dashboard |
pkg/mattermost/dashboards/mattermost-performance-kpi-metrics_rev2.json |
Adds KPI dashboard |
pkg/mattermost/dashboards/mattermost-performance-monitoring-bonus-metrics_rev2.json |
Adds bonus metrics dashboard |
pkg/mattermost/dashboards/mattermost-performance-monitoring-v2.json |
Adds server-performance dashboard |
pkg/mattermost/dashboards/mattermost-web-app-metrics.json |
Adds web dashboard |
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (3)
pkg/mattermost/monitoring.go:184
- In the calls-only path, this labels map aliases
Spec.ResourceLabels; merging discovery labels into it leaks ServiceMonitor-specific labels onto resources generated later in the reconcile. Clone the base labels before applying the rtcd discovery labels.
labels := mattermost.MattermostLabels(mattermost.Name)
pkg/mattermost/monitoring.go:258
- This map aliases
Spec.ResourceLabels, so adding the Grafana discovery/prune labels mutates the CR object and causes those ConfigMap-only labels to be copied onto the subsequently generated Deployment and pods. Changing dashboard discovery settings can therefore cause an unrelated workload rollout. Clone the base labels for each ConfigMap before modifying them.
labels := mattermost.MattermostLabels(mattermost.Name)
// The discovery label is what the Grafana sidecar watches for.
labels[discoveryLabel] = discoveryValue
// Stable marker (independent of the discovery label) for pruning.
labels[dashboardConfigMapLabel] = mattermost.Name
pkg/mattermost/monitoring.go:330
- When only PrometheusRule is enabled,
labelsaliasesSpec.ResourceLabels, and the rule-selector labels merged below leak into the Deployment/pod labels generated later. Clone the base label map before adding resource-specific labels.
labels := mattermost.MattermostLabels(mattermost.Name)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…, license scope - Rules scope by the dedicated metrics Service name (<name>-metrics) to match the Prometheus Operator's `service` target label. - MattermostLabels copies spec.resourceLabels instead of aliasing it. - License warning excludes callsMetrics (rtcd isn't Mattermost-license-gated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9a4a7a2 to
cde6d0f
Compare
…t CI The CEL validation suite needs a real apiserver+etcd (envtest); without the tag the default `go test` tried to start the control plane and failed in CI (no KUBEBUILDER_ASSETS). Tag it like the reconcile envtest — runs via make test-envtest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…atibility CI's e2e kind cluster runs a Kubernetes version that rejects x-kubernetes-validations in CRD schemas. The rtcdServiceSelector requirement is already enforced gracefully at runtime (reconcile logs and skips when empty), so remove the CEL marker to keep the CRD apply-able on older clusters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Mattermost CRD embeds full PodSpec/Container schemas and, with the monitoring
additions, its client-side `kubectl apply` last-applied-configuration annotation
exceeds the 256KB limit ("metadata.annotations: Too long"), breaking the e2e
deploy. Use server-side apply (--force-conflicts), which tracks ownership in
managedFields instead of the annotation. CI's kind is K8s v1.22 (SSA is GA).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The callsMetrics CEL validation was removed for CI-kind (K8s v1.22) compatibility, so the envtest asserting apiserver rejection no longer applies. rtcdServiceSelector is still enforced gracefully at runtime. The reconcile envtest remains. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review. This PR builds on #466; the shared findings (service-label scoping, Dashboard-specific:
Deferred (POC): the |
- Add TestClientMetricsDeploymentEnv: proves nil clientMetrics.enabled emits neither env var (server defaults), and explicit true/false emits both. - Validate serviceMonitor.interval as a Prometheus duration via a CRD pattern, so malformed input is rejected at the Mattermost API instead of later by the ServiceMonitor CRD (pattern is core OpenAPI, supported on the CI kind's K8s). - Document the MattermostServerDown no-endpoints limitation and why we avoid absent() (would false-alert in rule-only configs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5b61a78 to
31144bf
Compare
…disable Addresses the review gap that no controller test exercised checkMattermostMonitoring. Drives the reconcile against a fake client (Prometheus Operator types registered in the scheme, as in main.go): asserts the metrics Service, ServiceMonitor, and PrometheusRule are created when enabled and deleted when disabled, and that a repeat reconcile is a stable no-op. Runs in the standard go test suite (no envtest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MonitoringRequiresEnterpriseLicense warning is emitted via the event recorder, but the operator ClusterRole lacked events permission, so the event would be silently dropped. Add events create/patch.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 32 changed files in this pull request and generated 1 comment.
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (2)
controllers/mattermost/mattermost/monitoring.go:147
rtcdServiceSelectoris documented as required when calls metrics are enabled, but the CRD accepts an empty selector and this branch reports a successful reconcile without creating the requested ServiceMonitor. This also contradicts the stated runtime enforcement after CEL validation was removed; return a configuration error here (or add compatible API validation) instead of silently skipping it.
desired := mattermostApp.GenerateRtcdServiceMonitorV1Beta(mattermost)
if desired == nil {
reqLogger.Info("callsMetrics is enabled but rtcdServiceSelector is empty; skipping rtcd ServiceMonitor")
return nil
controllers/mattermost/mattermost/controller.go:75
- Omitting these watches means deletion or external modification of an owned ServiceMonitor or PrometheusRule never enqueues its Mattermost owner, so after the core resources become stable either object can remain missing/stale indefinitely. Keep startup safe by conditionally adding both watches when their REST mappings exist (and document that installing the CRDs later requires a controller restart).
// NOTE: ServiceMonitor is intentionally NOT added to Owns(). A watch on a
// type whose CRD is absent would fail the manager at startup; the Prometheus
// Operator CRDs are an optional dependency. GC still works via owner references.
…p update - test-envtest referenced the removed ./test/envtest package, so go test errored on the bad pattern before running the reconcile envtest. Drop it and the obsolete CEL run-filter; the target now runs the reconcile envtest. - Dashboard reconcile: refuse to overwrite a pre-existing ConfigMap this Mattermost does not own (metav1.IsControlledBy), matching the mimir fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 32 changed files in this pull request and generated 5 comments.
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (3)
pkg/resources/create_resources.go:222
- Deletion is based only on the generated rule name, so a Mattermost using another monitoring capability can delete an unrelated pre-existing
<name>-rulesPrometheusRule while this flag is off. Require the Mattermost owner and verifymetav1.IsControlledBybefore deleting.
controllers/mattermost/mattermost/monitoring.go:200 - A pre-existing
<name>-metricsService is overwritten here without checking whether this Mattermost controls it. Reject the name collision before updating, as the ConfigMap path already does.
current := &corev1.Service{}
if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, current); err != nil {
return errors.Wrap(err, "failed to fetch current metrics service")
}
controllers/mattermost/mattermost/monitoring.go:202
- Kubernetes populates
spec.clusterIPs(including["None"]for this headless Service) after creation, but newly generateddesiredobjects leave it empty. Any later reconcile that needs to update labels or other fields sends an object that clears this immutable field and fails; preserve the API-assigned value first, as the existing Service reconcile does.
return r.Resources.Update(current, desired, reqLogger)
Addresses Copilot re-review: cleanup-on-disable deleted by name and the create-then-update paths could adopt or overwrite an identically-named Service, ServiceMonitor, PrometheusRule, or dashboard ConfigMap the operator never created. - deleteOwnedResource: verify metav1.IsControlledBy before deleting; skip if not owned. Used for the metrics Service, main + rtcd ServiceMonitor, PrometheusRule. - ensureOwnedForUpdate: refuse to overwrite a pre-existing object with the generated name this Mattermost does not control. - Dashboard cleanup now also filters by IsControlledBy so a foreign ConfigMap carrying the discovery/marker label is never deleted. - Remove the now-unused DeleteServiceMonitor/DeletePrometheusRule helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 32 changed files in this pull request and generated 1 comment.
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (3)
controllers/mattermost/mattermost/monitoring.go:160
- This successfully reconciles an invalid
callsMetricsconfiguration even though the API declares the selector required. More importantly, if a selector is removed after a ServiceMonitor was created, this path leaves the old ServiceMonitor and its stale selector active indefinitely. Return a configuration error here (or explicitly delete the old resource) instead of silently skipping it.
if desired == nil {
reqLogger.Info("callsMetrics is enabled but rtcdServiceSelector is empty; skipping rtcd ServiceMonitor")
return nil
Makefile:130
- This target no longer exercises CEL validation—the CEL rule and its envtest were removed—so the comment now overstates what
test-envtestvalidates. Describe only the monitoring reconcile lifecycle covered bymonitoring_envtest_test.go.
# Docker). Covers CRD CEL validation and the monitoring reconcile create/delete
controllers/mattermost/mattermost/monitoring.go:318
anyMonitoringEnabledomits two capabilities that depend on the Enterprise-gated Mattermost metrics: an enabled Grafana dashboard and an explicitly enabledclientMetrics. Consequently, those configurations alone never emit the promised license warning. Include both while continuing to exclude the separately deployed rtcd metrics.
func anyMonitoringEnabled(mattermost *mmv1beta.Mattermost) bool {
return serviceMonitorEnabled(mattermost) ||
prometheusRuleEnabled(mattermost)
}
Addresses Copilot re-review: the metrics Service is headless (clusterIPs=["None"], API-assigned and immutable). Updating without preserving it made the next reconcile try to clear the field and fail. Reuse resources.CopyServiceEmptyAutoAssignedFields before the update, as the main Service reconcile already does.
…abels, SSA) - rtcd: when callsMetrics is enabled but rtcdServiceSelector is cleared, delete any owned <name>-rtcd ServiceMonitor instead of leaving it orphaned. - Generators: guard GenerateServiceMonitor/RtcdServiceMonitor/PrometheusRule against a nil Spec.Monitoring so direct/test callers don't panic (reconcile already guards). - Labels: copy spec.resourceLabels in MattermostPodLabels and MattermostJobServerPodLabels too (they still aliased and mutated the user's map, which undercut the MattermostLabels fix). - Makefile: server-side apply with a stable --field-manager and no --force-conflicts, so a genuine conflict with GitOps/Helm errors instead of silently clobbering; document the one-time client-side->SSA migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 32 changed files in this pull request and generated no new comments.
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (3)
pkg/mattermost/monitoring.go:253
- This exported generator dereferences
Spec.Monitoringwithout the nil guard used by the other monitoring generators. Calling it for a CR with no monitoring block panics; the shared nil-monitoring test also omits this generator. GuardSpec.Monitoringbefore readingGrafanaDashboard.
if gd := mattermost.Spec.Monitoring.GrafanaDashboard; gd != nil {
if gd.DiscoveryLabel != "" {
discoveryLabel = gd.DiscoveryLabel
}
if gd.DiscoveryLabelValue != "" {
discoveryValue = gd.DiscoveryLabelValue
}
}
pkg/mattermost/monitoring.go:377
- The CR name is inserted into a PromQL regex without escaping. Kubernetes object names may contain
., which is a regex wildcard, so a Mattermost namedmm.prodcan make these resource alerts match pods such asmmXprod-*in the same namespace. Quote the name withregexp.QuoteMetabefore appending the pod suffix pattern.
rulePlaceholderPodSelector: mattermost.Name + "-[^-]+-[^-]+",
controllers/mattermost/mattermost/monitoring.go:326
- This predicate claims to cover every Enterprise-gated monitoring capability, but it omits Grafana dashboards and explicitly enabled client metrics. With either capability enabled alone, the operator emits no license warning even though the shipped dashboards/client metrics depend on the licensed Mattermost
/metricsendpoint. IncludegrafanaDashboardEnabledand an explicit-true client-metrics check.
func anyMonitoringEnabled(mattermost *mmv1beta.Mattermost) bool {
return serviceMonitorEnabled(mattermost) ||
prometheusRuleEnabled(mattermost)
}
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughChangesMattermost monitoring
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant MattermostReconciler
participant MonitoringGenerators
participant KubernetesAPI
participant PrometheusOperator
MattermostReconciler->>MonitoringGenerators: Generate enabled monitoring resources
MonitoringGenerators->>KubernetesAPI: Reconcile metrics Service and dashboard ConfigMaps
MonitoringGenerators->>PrometheusOperator: Reconcile ServiceMonitor and PrometheusRule
MattermostReconciler->>KubernetesAPI: Remove disabled or stale owned resources
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (11)
hack/transform_dashboards.py (3)
33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSilence the false positive from Ruff.
Ruff reports S105 for
EXPR_DS_TOKENbecause the name containsTOKEN. The value is a Grafana datasource placeholder, not a secret. Add a suppression comment so the lint run stays clean.🔧 Proposed suppression
-EXPR_DS_TOKEN = "${DS_EXPRESSION}" +EXPR_DS_TOKEN = "${DS_EXPRESSION}" # noqa: S105 - Grafana datasource placeholder, not a secret🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/transform_dashboards.py` at line 33, Add an inline Ruff S105 suppression to the EXPR_DS_TOKEN constant declaration, documenting that this Grafana datasource placeholder is not a secret while leaving its name and value unchanged.Source: Linters/SAST tools
54-62: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winPanel queries stay unscoped by namespace.
podify_server_varscopes theservervariable options by$namespace.replace_instance_filterrewrites panel filters topod=~"$server"only. The panel queries therefore select by pod name across all namespaces. If two namespaces expose Mattermost pods with the same name, the panels mix series from both.Consider rewriting the filter to include the namespace selector.
♻️ Proposed change to scope panel queries
def replace_instance_filter(node): - """Recursively rewrite instance=~"$server" -> pod=~"$server" in query strings.""" + """Recursively rewrite instance=~"$server" -> namespace/pod scoped filter.""" if isinstance(node, dict): return {k: replace_instance_filter(v) for k, v in node.items()} if isinstance(node, list): return [replace_instance_filter(v) for v in node] if isinstance(node, str): - return node.replace('instance=~"$server"', 'pod=~"$server"') + return node.replace( + 'instance=~"$server"', + 'namespace="$namespace",pod=~"$server"', + ) return nodeThe packaged dashboard JSON files must be regenerated after this change.
Also applies to: 102-116
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/transform_dashboards.py` around lines 54 - 62, Update replace_instance_filter so rewritten panel filters include the $namespace selector alongside pod=~"$server", ensuring queries remain namespace-scoped. Preserve the recursive handling of dictionaries, lists, strings, and other values, and regenerate the packaged dashboard JSON files afterward.
119-143: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
transformis not idempotent.Each call prepends a new
datasourcevariable.transformchecks for an existingnamespacevariable but not for an existingdatasourcevariable. If the script runs on an already-transformed directory, the output gains a duplicate variable.♻️ Proposed guard
- leading = [datasource_variable()] + leading = [] + if not any(v.get("name") == DATASOURCE_VAR_NAME for v in templating): + leading.append(datasource_variable()) if is_server_dashboard and not any(v.get("name") == "namespace" for v in templating): leading.append(namespace_variable())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/transform_dashboards.py` around lines 119 - 143, The transform function should avoid duplicating the datasource variable when processing an already-transformed dashboard. Before prepending the result of datasource_variable(), check templating for an existing variable named "datasource" and only add it when absent, while preserving the existing namespace guard and ordering behavior.pkg/mattermost/mattermost_v1beta_test.go (1)
1363-1369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd the remaining nil cases.
GenerateDeploymentV1Betaguards three conditions:Monitoring != nil,ClientMetrics != nil, andClientMetrics.Enabled != nil. This subtest covers only the third condition. Add cases for a nilMonitoringand a nilClientMetricsso a future refactor cannot drop a nil check without a test failure.💚 Proposed additional subtests
+ t.Run("nil Monitoring emits neither variable", func(t *testing.T) { + mm := &mmv1beta.Mattermost{Spec: mmv1beta.MattermostSpec{}} + _, hasClient := mattermostContainerEnv(t, mm, "MM_METRICSSETTINGS_ENABLECLIENTMETRICS") + _, hasNotif := mattermostContainerEnv(t, mm, "MM_METRICSSETTINGS_ENABLENOTIFICATIONMETRICS") + assert.False(t, hasClient) + assert.False(t, hasNotif) + }) + + t.Run("nil ClientMetrics emits neither variable", func(t *testing.T) { + mm := withClientMetrics(nil) + _, hasClient := mattermostContainerEnv(t, mm, "MM_METRICSSETTINGS_ENABLECLIENTMETRICS") + _, hasNotif := mattermostContainerEnv(t, mm, "MM_METRICSSETTINGS_ENABLENOTIFICATIONMETRICS") + assert.False(t, hasClient) + assert.False(t, hasNotif) + }) + t.Run("nil Enabled emits neither variable (server defaults preserved)", func(t *testing.T) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/mattermost/mattermost_v1beta_test.go` around lines 1363 - 1369, Add subtests covering nil Monitoring and nil ClientMetrics in the GenerateDeploymentV1Beta test suite, alongside the existing nil Enabled case. Verify each configuration emits neither client nor notification metrics environment variable, preserving server defaults and guarding both corresponding nil checks.pkg/mattermost/dashboards/mattermost-performance-kpi-metrics_rev2.json (1)
630-662: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLegacy dashboard alert blocks are inert.
These
alertobjects use the schemaVersion 16 dashboard alerting format. Grafana 9 and later removed legacy dashboard alerting, so Grafana ignores these blocks. They also keep the hidden helper targets at lines 720-726 and 858-864 alive for no purpose.The operator ships these dashboards through the Grafana sidecar, and the PR verifies against kube-prometheus-stack, which runs a recent Grafana. Consider stripping the
alertblocks and their helper targets inhack/transform_dashboards.py, then regenerating this file.Also applies to: 778-810
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/mattermost/dashboards/mattermost-performance-kpi-metrics_rev2.json` around lines 630 - 662, Remove the legacy dashboard alert blocks and their associated hidden helper targets from the dashboard transformation logic in hack/transform_dashboards.py, including both alert sections represented here. Regenerate mattermost-performance-kpi-metrics_rev2.json so the resulting dashboard contains neither obsolete alert objects nor unused helper targets, while preserving all other dashboard queries and panels.pkg/mattermost/mattermost_v1beta.go (1)
353-371: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOne field drives two server settings.
ClientMetrics.Enabledsets bothMM_METRICSSETTINGS_ENABLECLIENTMETRICSandMM_METRICSSETTINGS_ENABLENOTIFICATIONMETRICS. A user cannot enable client metrics and disable notification metrics.The nil handling is correct, and
mergeEnvVarsat line 401 still letsspec.mattermostEnvoverride both values. Adding a separate field later is an additive API change, so this is safe for the proof of concept. Record the limitation in the field documentation so users understand the coupling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/mattermost/mattermost_v1beta.go` around lines 353 - 371, Document in the API field documentation for Monitoring.ClientMetrics.Enabled that it controls both client/RUM metrics and notification performance metrics, so users understand they cannot configure them independently. Preserve the existing nil handling and environment-variable generation in the Mattermost deployment logic.controllers/mattermost/mattermost/monitoring.go (2)
253-263: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
anyMonitoringEnabledomitsgrafanaDashboard, which contradicts the stated rule.The doc comment on
anyMonitoringEnabledstates the function reports whether any capability that depends on the Enterprise-gated/metricsendpoint is on. The Grafana dashboards query the same Mattermost metrics. If a user enables onlygrafanaDashboardand sets nolicenseSecret, the dashboards render empty and no warning event is emitted.If the omission is deliberate, record the reason in the doc comment. Otherwise include the flag.
♻️ Proposed change
func anyMonitoringEnabled(mattermost *mmv1beta.Mattermost) bool { return serviceMonitorEnabled(mattermost) || - prometheusRuleEnabled(mattermost) + prometheusRuleEnabled(mattermost) || + grafanaDashboardEnabled(mattermost) }Also applies to: 319-326
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@controllers/mattermost/mattermost/monitoring.go` around lines 253 - 263, Update anyMonitoringEnabled to include the grafanaDashboard setting when determining whether Enterprise-gated monitoring is enabled, so warnMonitoringWithoutLicense emits the existing warning and event for dashboard-only configurations without a license. Keep the current behavior for all other monitoring flags.
128-129: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCentralize monitoring resource names.
Export shared name helpers from
pkg/mattermostand use them in generators and cleanup keys. This prevents future suffix changes from causing cleanup no-ops.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@controllers/mattermost/mattermost/monitoring.go` around lines 128 - 129, Centralize the Mattermost monitoring resource naming by adding exported name helpers in pkg/mattermost, then update the monitoring generators and cleanup keys around metricsSvcKey and smKey to use those helpers instead of constructing names inline. Ensure all consumers share the same helper-generated names so future suffix changes remain consistent.controllers/mattermost/mattermost/monitoring_envtest_test.go (1)
92-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider asserting the exact dashboard count.
GreaterOrEqual(..., 9)passes if the reconcile creates extra ConfigMaps. An exact count also detects duplicate or unpruned dashboards.♻️ Proposed change
- assert.GreaterOrEqual(t, len(cms.Items), 9, "expected one ConfigMap per embedded dashboard") + assert.Len(t, cms.Items, 9, "expected exactly one ConfigMap per embedded dashboard")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@controllers/mattermost/mattermost/monitoring_envtest_test.go` at line 92, Update the assertion in the monitoring reconciliation test to require exactly 9 ConfigMaps, replacing the lower-bound check while preserving the existing failure message and dashboard-count expectation.Makefile (2)
258-258: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winUse an explicit dry-run strategy
The bare
--dry-runform remains supported, but it is deprecated. Use--dry-run=client -o yamlfor forward compatibility.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Makefile` at line 258, Update the kubectl namespace creation command to use the explicit client-side dry-run mode with YAML output, replacing the deprecated bare --dry-run form while preserving the existing apply pipeline.
136-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPin
setup-envtestto thev0.21.0commit.The nested module does not publish a
v0.21.0tag. Usev0.0.0-20250520071515-71f7db556ca5, which resolves to thecontroller-runtimev0.21.0commit, instead oflatest.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Makefile` around lines 136 - 137, Update the $(SETUP_ENVTEST) Makefile target to install setup-envtest at version v0.0.0-20250520071515-71f7db556ca5 instead of latest, preserving the existing GOBIN and GO_INSTALL invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@controllers/mattermost/mattermost/mattermost.go`:
- Around line 64-68: Reorder the checkMattermost flow so
checkMattermostDeployment runs before checkMattermostMonitoring. Keep monitoring
errors returned from checkMattermost after the deployment reconciliation,
ensuring monitoring remains reported while failures in the optional add-on do
not block the primary Mattermost workload from converging.
In
`@pkg/mattermost/dashboards/mattermost-collapsed_reply_threads_performance.json`:
- Around line 30-83: Update the dashboard transformation flow in
hack/transform_dashboards.py, specifically replace_ds_strings, to add {"type":
"prometheus", "uid": "${datasource}"} when panels or targets lack a datasource.
Regenerate the dashboard so panels with ids 2 and 4 and their targets honor the
datasource variable consistently with panels 6 and 8.
In `@pkg/mattermost/dashboards/mattermost-mobile-performance.json`:
- Around line 1981-2000: Replace the unresolved ${VAR_RATE} value and text in
the rate constant with a concrete PromQL duration, then regenerate the dashboard
so all $rate targets use that valid duration. Also update the __inputs handling
in transform_dashboards.py to resolve constant defaults before removing the
inputs metadata, preventing future exports from retaining unresolved
placeholders.
In `@pkg/mattermost/monitoring.go`:
- Around line 242-246: Update GenerateGrafanaDashboardConfigMapsV1Beta to check
mattermost.Spec.Monitoring for nil before accessing GrafanaDashboard, returning
nil, nil when absent. Extend TestGeneratorsNilMonitoring to cover this behavior.
- Around line 364-379: The pod selector built in rulePlaceholders must escape
mattermost.Name before embedding it in the PromQL regular expression. Use
regexp.QuoteMeta for the name while preserving the existing suffix pattern, and
add coverage for a dotted Mattermost name to ensure literal dots do not match
arbitrary characters.
- Around line 262-266: Validate the configured DiscoveryLabel before generating
ConfigMaps, rejecting it when it equals the reserved dashboardConfigMapLabel
value "mattermost.com/grafana-dashboard". Add this guard in the monitoring flow
before the label assignments around MattermostLabels, and return the existing
error type or propagation path so ConfigMap generation does not proceed with the
conflicting label.
---
Nitpick comments:
In `@controllers/mattermost/mattermost/monitoring_envtest_test.go`:
- Line 92: Update the assertion in the monitoring reconciliation test to require
exactly 9 ConfigMaps, replacing the lower-bound check while preserving the
existing failure message and dashboard-count expectation.
In `@controllers/mattermost/mattermost/monitoring.go`:
- Around line 253-263: Update anyMonitoringEnabled to include the
grafanaDashboard setting when determining whether Enterprise-gated monitoring is
enabled, so warnMonitoringWithoutLicense emits the existing warning and event
for dashboard-only configurations without a license. Keep the current behavior
for all other monitoring flags.
- Around line 128-129: Centralize the Mattermost monitoring resource naming by
adding exported name helpers in pkg/mattermost, then update the monitoring
generators and cleanup keys around metricsSvcKey and smKey to use those helpers
instead of constructing names inline. Ensure all consumers share the same
helper-generated names so future suffix changes remain consistent.
In `@hack/transform_dashboards.py`:
- Line 33: Add an inline Ruff S105 suppression to the EXPR_DS_TOKEN constant
declaration, documenting that this Grafana datasource placeholder is not a
secret while leaving its name and value unchanged.
- Around line 54-62: Update replace_instance_filter so rewritten panel filters
include the $namespace selector alongside pod=~"$server", ensuring queries
remain namespace-scoped. Preserve the recursive handling of dictionaries, lists,
strings, and other values, and regenerate the packaged dashboard JSON files
afterward.
- Around line 119-143: The transform function should avoid duplicating the
datasource variable when processing an already-transformed dashboard. Before
prepending the result of datasource_variable(), check templating for an existing
variable named "datasource" and only add it when absent, while preserving the
existing namespace guard and ordering behavior.
In `@Makefile`:
- Line 258: Update the kubectl namespace creation command to use the explicit
client-side dry-run mode with YAML output, replacing the deprecated bare
--dry-run form while preserving the existing apply pipeline.
- Around line 136-137: Update the $(SETUP_ENVTEST) Makefile target to install
setup-envtest at version v0.0.0-20250520071515-71f7db556ca5 instead of latest,
preserving the existing GOBIN and GO_INSTALL invocation.
In `@pkg/mattermost/dashboards/mattermost-performance-kpi-metrics_rev2.json`:
- Around line 630-662: Remove the legacy dashboard alert blocks and their
associated hidden helper targets from the dashboard transformation logic in
hack/transform_dashboards.py, including both alert sections represented here.
Regenerate mattermost-performance-kpi-metrics_rev2.json so the resulting
dashboard contains neither obsolete alert objects nor unused helper targets,
while preserving all other dashboard queries and panels.
In `@pkg/mattermost/mattermost_v1beta_test.go`:
- Around line 1363-1369: Add subtests covering nil Monitoring and nil
ClientMetrics in the GenerateDeploymentV1Beta test suite, alongside the existing
nil Enabled case. Verify each configuration emits neither client nor
notification metrics environment variable, preserving server defaults and
guarding both corresponding nil checks.
In `@pkg/mattermost/mattermost_v1beta.go`:
- Around line 353-371: Document in the API field documentation for
Monitoring.ClientMetrics.Enabled that it controls both client/RUM metrics and
notification performance metrics, so users understand they cannot configure them
independently. Preserve the existing nil handling and environment-variable
generation in the Mattermost deployment logic.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7fef7de4-252b-4d82-8ba3-5ac38200a417
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (31)
Makefileapis/mattermost/v1beta1/mattermost_types.goapis/mattermost/v1beta1/mattermost_utils.goapis/mattermost/v1beta1/zz_generated.deepcopy.goapis/mattermost/v1beta1/zz_generated.openapi.goconfig/crd/bases/installation.mattermost.com_mattermosts.yamlconfig/rbac/role.yamlcontrollers/mattermost/mattermost/controller.gocontrollers/mattermost/mattermost/mattermost.gocontrollers/mattermost/mattermost/monitoring.gocontrollers/mattermost/mattermost/monitoring_envtest_test.gocontrollers/mattermost/mattermost/monitoring_reconcile_test.godocs/examples/mattermost_monitoring.yamlgo.modhack/transform_dashboards.pymain.gopkg/mattermost/dashboards/mattermost-calls-performance-monitoring.jsonpkg/mattermost/dashboards/mattermost-collapsed_reply_threads_performance.jsonpkg/mattermost/dashboards/mattermost-desktop-app-metrics.jsonpkg/mattermost/dashboards/mattermost-mobile-performance.jsonpkg/mattermost/dashboards/mattermost-notification-metrics.jsonpkg/mattermost/dashboards/mattermost-performance-kpi-metrics_rev2.jsonpkg/mattermost/dashboards/mattermost-performance-monitoring-bonus-metrics_rev2.jsonpkg/mattermost/dashboards/mattermost-performance-monitoring-v2.jsonpkg/mattermost/dashboards/mattermost-web-app-metrics.jsonpkg/mattermost/mattermost_v1beta.gopkg/mattermost/mattermost_v1beta_test.gopkg/mattermost/monitoring.gopkg/mattermost/monitoring_test.gopkg/mattermost/prometheusrules/mattermost.yamlpkg/resources/create_resources.go
…n pod regex Addresses CodeRabbit: - Reconcile the opt-in monitoring resources AFTER checkMattermostDeployment so a recoverable monitoring error (e.g. a name collision) never blocks the core Mattermost Deployment from converging; the error still surfaces on the result. - QuoteMeta the Mattermost name before embedding it in the PromQL pod=~ regex — names may contain dots (a metacharacter), so "test.mm" must not match "testXmm-..." pods from another install. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GenerateGrafanaDashboardConfigMapsV1Beta: return nil when spec.monitoring is unset, matching the other generators' nil guard. - Reject a grafanaDashboard.discoveryLabel that collides with the reserved sidecar key, instead of silently overwriting it. - Dashboard transform: inject an explicit prometheus datasource ref on panels/targets that shipped without one, so they no longer fall back to Grafana's default once datasources collapse onto the templated var. - Dashboard transform: resolve constant template vars orphaned by dropping __inputs -- pin metrics ports to their literal defaults (8045/8067) and inline the rate interval as $__rate_interval (single-pass interpolation can't resolve a built-in via a constant). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hema Regenerated via `make yaml` so the bundle carries the full spec.monitoring schema (including grafanaDashboard) and the servicemonitors/prometheusrules/ events RBAC. The committed bundle was stale (Copilot review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 33 changed files in this pull request and generated no new comments.
Files not reviewed (2)
- apis/mattermost/v1beta1/zz_generated.deepcopy.go: Generated file
- apis/mattermost/v1beta1/zz_generated.openapi.go: Generated file
Suppressed comments (4)
controllers/mattermost/mattermost/monitoring.go:163
rtcdServiceSelectoris documented as required when Calls metrics is enabled, but this path treats the invalid configuration as a successful reconcile and silently creates no ServiceMonitor. That leaves the CR appearing healthy while the requested capability is absent. After cleaning up any previous object, return an error (or reject the configuration at admission) so the requirement is actually enforced.
desired := mattermostApp.GenerateRtcdServiceMonitorV1Beta(mattermost)
if desired == nil {
// callsMetrics is enabled but the selector was cleared: tear down any rtcd
// ServiceMonitor we previously created rather than leaving it orphaned.
reqLogger.Info("callsMetrics is enabled but rtcdServiceSelector is empty; removing any owned rtcd ServiceMonitor")
return r.deleteOwnedResource(mattermost, rtcdKey, &monitoringv1.ServiceMonitor{}, reqLogger)
controllers/mattermost/mattermost/monitoring.go:326
- A dashboard-only configuration is omitted from the Enterprise-license warning even though every shipped Mattermost dashboard depends on the license-gated
/metricsendpoint. WithgrafanaDashboard.enabled: trueand no ServiceMonitor or rule, the operator provisions dashboards that have no Mattermost data without emitting the warning promised by this helper.
func anyMonitoringEnabled(mattermost *mmv1beta.Mattermost) bool {
return serviceMonitorEnabled(mattermost) ||
prometheusRuleEnabled(mattermost)
Makefile:130
- This target no longer runs any CEL validation: the CEL rule/test was removed, and the command now filters only the controller monitoring tests. Keeping this claim makes the target's validation scope misleading.
# Fast local validation with envtest (a real apiserver+etcd from binaries — no
# Docker). Covers CRD CEL validation and the monitoring reconcile create/delete
# lifecycle. Downloads the apiserver binaries on first run.
hack/transform_dashboards.py:272
- The transform only overwrites dashboards present in
src_dir; it never removes JSON files that disappeared or were renamed upstream. Re-running it into the embedded dashboard directory therefore leaves stale dashboards in the binary, so the reconcile-time prune cannot observe the removal. Make the destination a mirror of the source set before writing outputs (while preserving safe behavior when source and destination are the same directory).
src, dst = Path(sys.argv[1]), Path(sys.argv[2])
dst.mkdir(parents=True, exist_ok=True)
for path in sorted(src.glob("*.json")):
Summary
Proof-of-concept (draft) for SRE review — the "monitoring with Grafana dashboards" variant.
Builds on #466 (rules + ServiceMonitor) and adds the Grafana dashboard layer, so the two branches differ only by this feature — letting SRE weigh adopting dashboards separately from rules/ServiceMonitor.
spec.monitoring.grafanaDashboard(enable + sidecar discovery label/value) and the reconcile path.mattermost-performance-assets, transformed for Kubernetes byhack/transform_dashboards.py:${DS_*}inputs onto one templateddatasourcevariable and drop__inputs/__requires, so the Grafana sidecar provisions them without an import prompt.pod(scoped by a newnamespacevariable) instead of the churninginstanceIP:port (123instance=~"$server"filters rewritten topod=~"$server").Validation: unit tests; an
envtestreconcile test asserting the ConfigMaps are created and deleted; and a livekind+ kube-prometheus-stack run confirming the stock Grafana sidecar imports and provisions all nine dashboards (/api/search).Dashboard-only delta vs the base branch:
Ticket Link
Internal SRE monitoring POC — no public ticket. Companion to #466.
Release Note
🤖 Generated with Claude Code