feat(#3494): provision the agents dashboard from the repo

Grafana served no dashboards: only datasources were provisioned, while
the module header already claimed dashboards were. This ships the agents
dashboard as a file provider and makes that sentence true.

The datasource uid is bound once and substituted into the dashboard at
build time. Committing the literal would make the dashboard a second
speller of a name the datasource already owns, and the drift failure is
silent -- panels render empty rather than erroring.

The shipped copy drops the `DS` datasource variable: it exists so an
operator can pick a store on manual import, and a provisioned dashboard
must not ask.
This commit is contained in:
atlas 2026-08-23 22:21:34 +02:00 committed by mara
commit 9fec1e1dca
2 changed files with 704 additions and 2 deletions

View file

@ -5,7 +5,9 @@
# restarted, reconfigured or broken without taking the TSDB down with it.
# They are a pair, not a unit.
#
# Dashboards are provisioned from config, deliberately not deployed from git.
# Dashboards are provisioned from files in this repo, deliberately not created
# in the UI: the store path is immutable, so Grafana is told not to accept UI
# edits it would lose on the next rebuild.
{
pkgs,
lib,
@ -24,6 +26,32 @@ let
caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
# Spelled ONCE. The provisioned datasource declares it and every panel in the
# provisioned dashboards references it; a second literal would be free to
# drift, and the failure is a dashboard that renders with empty panels rather
# than an error.
datasourceUid = "swarm-victoriametrics";
# The shipped dashboards carry `@datasourceUid@` where a real deployment needs
# the uid above. They are substituted here rather than committed with the
# literal so the single binding stays single.
renderDashboard =
name:
pkgs.writeText name (
builtins.replaceStrings [ "@datasourceUid@" ] [ datasourceUid ] (
builtins.readFile (./swarm-grafana/dashboards + "/${name}")
)
);
# Grafana's file provider wants a DIRECTORY to scan, so the rendered files are
# collected into one.
dashboardDir = pkgs.linkFarm "hyperhive-grafana-dashboards" (
map (name: {
inherit name;
path = renderDashboard name;
}) [ "agents.json" ]
);
# Total on a null swarm domain for the same reason every sibling module is:
# the required-domain assertion in hive-network.nix should be what an
# operator sees, not a coercion error from here.
@ -569,13 +597,28 @@ in
{
name = "VictoriaMetrics";
type = "prometheus";
uid = "swarm-victoriametrics";
uid = datasourceUid;
url = cfg.datasourceUrl;
access = "proxy";
isDefault = true;
}
];
};
provision.dashboards.settings = {
apiVersion = 1;
providers = [
{
name = "hyperhive";
type = "file";
# Read-only in the UI: the store path is immutable, so an edit
# saved here would be silently discarded on the next rebuild.
# Better to refuse the edit than to lose it.
allowUiUpdates = false;
options.path = dashboardDir;
}
];
};
};
};
};