aboutsummaryrefslogtreecommitdiffstats
path: root/flake.nix
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2026-08-27 14:16:02 -0500
committerTim Keller <tjk@tjkeller.xyz>2026-08-27 14:16:02 -0500
commitca193ad52b2ee2985af079be0159cc679c914f02 (patch)
tree9e9d1e421b6b55f4735e4559c5b4a6c0055a80ba /flake.nix
parentd2d1386abb358fa547a40fc8df613d799155b56d (diff)
downloadtelemetry-flake-ca193ad52b2ee2985af079be0159cc679c914f02.tar.xz
telemetry-flake-ca193ad52b2ee2985af079be0159cc679c914f02.zip
generate the dashboards at build time instead of committing them
The JSON is machine-written and the generator beside it is the only source of truth, so committing both only created a way for them to drift -- which is what the dashboards-current check existed to police. modules/dashboards.nix now runs gen_zfs.py / gen_smart.py in the derivation, so a stale dashboard cannot be built at all and the check is gone. Note there is no third option: flakes copy only git-tracked files into the store, so a gitignored dashboards/*.json cannot be read from disk either. It is generate, or commit. Verified equivalent -- both generated dashboards are byte-identical to the JSON this commit deletes. Adds a dashboards-nonempty check. Factoring the shared jq invocation out first introduced a bug it caught: the multi-line string ended in a newline, so the callers' `< $src > "$out"` landed on a line of its own as a separate command, truncating every dashboard to zero bytes while the build still succeeded. All four provisioned dashboards were empty. jqFixup is now a single line, and the check asserts each dashboard is non-empty and has a title, uid and panels. Both failure modes are exercised: an empty dashboard and one with no panels each fail the check. It also asserts it found four files, because the first version globbed *.json against linkFarm entries named without the suffix and passed vacuously. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKczN1f66kmLxNBLX37voQ
Diffstat (limited to 'flake.nix')
-rw-r--r--flake.nix125
1 files changed, 65 insertions, 60 deletions
diff --git a/flake.nix b/flake.nix
index 1a9c661..a8b5101 100644
--- a/flake.nix
+++ b/flake.nix
@@ -29,68 +29,73 @@
in collectors // { default = collectors.zfs-metrics; }
);
- checks = forAllSystems (pkgs: {
- # promtool over the recording rules. The server module also runs this at build
- # time via services.prometheus.checkConfig, but that only fires for people who
- # actually deploy it.
- prometheus-rules = pkgs.runCommand "check-prometheus-rules"
- { nativeBuildInputs = [ pkgs.prometheus.cli ]; }
- ''
- promtool check rules ${./rules}/*.yml
- touch "$out"
- '';
+ checks = forAllSystems (pkgs:
+ let
+ dashboardsForCheck = pkgs.linkFarm "dashboards" (pkgs.lib.mapAttrsToList
+ # The .json suffix matters: the check globs *.json, and a
+ # linkFarm named without it silently matches nothing.
+ (name: drv: { name = "${name}.json"; path = drv; })
+ # callPackages, not callPackage: the latter adds override/
+ # overrideDerivation attrs that are functions, not derivations.
+ (pkgs.callPackages ./modules/dashboards.nix {
+ dsUid = { prometheus = "prometheus"; loki = "loki"; };
+ }));
+ in
+ {
+ # promtool over the recording rules. The server module also runs this at build
+ # time via services.prometheus.checkConfig, but that only fires for people who
+ # actually deploy it.
+ prometheus-rules = pkgs.runCommand "check-prometheus-rules"
+ { nativeBuildInputs = [ pkgs.prometheus.cli ]; }
+ ''
+ promtool check rules ${./rules}/*.yml
+ touch "$out"
+ '';
- # The dashboard JSON is generated by the scripts next to it and committed, so it
- # can rot silently if someone edits a generator without re-running it. Regenerate
- # here and diff.
- dashboards-current = pkgs.runCommand "check-dashboards-current"
- { nativeBuildInputs = [ pkgs.python3 pkgs.diffutils ]; }
- ''
- cp -r ${./dashboards} work
- chmod -R u+w work
- cd work
+ # An empty or malformed dashboard does not fail any build on its own -- it
+ # provisions fine and simply renders nothing -- so assert on the content.
+ dashboards-nonempty = pkgs.runCommand "check-dashboards-nonempty"
+ { nativeBuildInputs = [ pkgs.jq ]; }
+ ''
+ shopt -s nullglob
+ found=0
+ for f in ${dashboardsForCheck}/*.json; do
+ found=$((found + 1))
+ name=$(basename "$f")
+ [ -s "$f" ] || { echo "$name is empty"; exit 1; }
+ jq -e '.title and .uid and (.panels | length > 0)' "$f" >/dev/null \
+ || { echo "$name has no title, uid or panels"; exit 1; }
+ echo "$name: $(jq -r '"\(.title) — \(.panels | length) panels"' "$f")"
+ done
+ # A glob that matches nothing would otherwise make this check vacuous.
+ [ "$found" -eq 4 ] || { echo "expected 4 dashboards, found $found"; exit 1; }
+ touch "$out"
+ '';
- # Note the loop variable is not called "out": that is the derivation's own
- # output path, and shadowing it makes the final touch silently write
- # somewhere else and the build fail with "failed to produce output path".
- check() {
- local generator="$1" committed="$2"
- python3 "$generator" > regenerated.json
- if ! diff -u "$committed" regenerated.json; then
- echo
- echo "dashboards/$committed is stale."
- echo "Re-run: python3 dashboards/$generator > dashboards/$committed"
- exit 1
- fi
- }
-
- check gen_smart.py smart.json
- check gen_zfs.py zfs-pools.json
- touch "$out"
- '';
-
- # Both halves have to evaluate, separately and together. Catches option typos
- # and missing mkIf guards without needing a real machine.
- modules-eval = (nixpkgs.lib.nixosSystem {
- system = pkgs.stdenv.hostPlatform.system;
- modules = [
- self.nixosModules.default
- ({ lib, ... }: {
- services.telemetry.collectors.enable = true;
- services.telemetry.server = {
- enable = true;
- collectorHosts = [ "one" "two" ];
- proxy.enable = true;
- # Never read during evaluation; only has to satisfy the assertion.
- grafana.secretKeyFile = "/run/secrets/grafana-secret-key";
- };
- boot.loader.grub.devices = [ "/dev/sda" ];
- fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
- system.stateVersion = "25.11";
- })
- ];
- }).config.system.build.toplevel;
- });
+ # Both halves have to evaluate, separately and together. Catches option typos
+ # and missing mkIf guards without needing a real machine -- and because the
+ # dashboards are generated during the build, this also runs both generators and
+ # fails if either one crashes or emits something jq cannot parse.
+ modules-eval = (nixpkgs.lib.nixosSystem {
+ system = pkgs.stdenv.hostPlatform.system;
+ modules = [
+ self.nixosModules.default
+ ({ lib, ... }: {
+ services.telemetry.collectors.enable = true;
+ services.telemetry.server = {
+ enable = true;
+ collectorHosts = [ "one" "two" ];
+ proxy.enable = true;
+ # Never read during evaluation; only has to satisfy the assertion.
+ grafana.secretKeyFile = "/run/secrets/grafana-secret-key";
+ };
+ boot.loader.grub.devices = [ "/dev/sda" ];
+ fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
+ system.stateVersion = "25.11";
+ })
+ ];
+ }).config.system.build.toplevel;
+ });
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {