From 38ee68c31d44ada7a56e2ea6a0be5195cbb487c7 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 14 Oct 2026 02:23:15 +0200 Subject: [PATCH] hive otel: keep file_storage defined when the collector authenticates upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hive collector's `settings` was `{ extensions.file_storage = …; … } // lib.optionalAttrs senderAuth { extensions.oauth2client/swarm = …; }`. `//` is shallow, so whenever senderAuth held (every valid hive config) the right-hand `extensions` replaced the left one and `file_storage` vanished, while `service.extensions` still listed it. `otelcol validate` then failed the config.yaml build: Error: service::extensions: references extension "file_storage" which is not configured Broken by 02831fb5e (PR #5178), which added `extensions.file_storage` to the left-hand attrset; the `//` block for the auth extension predates it (cb787997bd). Both extensions now live in one `extensions` attrset. module-eval-hive-otel gains a case that every name in `service.extensions` is defined under `extensions`; it evaluates false on the parent commit and true here. Fixes #5240 --- nix/host-modules/otel.nix | 67 +++++++++++++++++++---------------- nix/module-eval/hive-otel.nix | 10 ++++++ 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/nix/host-modules/otel.nix b/nix/host-modules/otel.nix index bea8748c..a87b81d2 100644 --- a/nix/host-modules/otel.nix +++ b/nix/host-modules/otel.nix @@ -401,19 +401,42 @@ in # prove this config STARTS, never mind that a sample arrives. validateConfigFile = true; settings = { - # The journald receiver's cursor store, same shape as the agent - # tier's own (`nix/agent-modules/otel.nix`): a restart resumes - # from the last-read position instead of re-reading from the - # journal's tail (silently dropping whatever landed while the - # collector was down) or its head (re-sending everything). - # `create_directory` matters at BUILD time: `validateConfigFile` - # runs `otelcol validate` in the nix sandbox, where systemd's - # `StateDirectory=` has never run and the path does not exist - # yet — without it the validator refuses with "directory must - # exist". - extensions.file_storage = { - directory = "/var/lib/opentelemetry-collector"; - create_directory = true; + # Every extension is defined in this one attrset: a second + # `extensions` joined onto `settings` with `//` replaces this one + # instead of adding to it. + extensions = { + # The journald receiver's cursor store, same shape as the agent + # tier's own (`nix/agent-modules/otel.nix`): a restart resumes + # from the last-read position instead of re-reading from the + # journal's tail (silently dropping whatever landed while the + # collector was down) or its head (re-sending everything). + # `create_directory` matters at BUILD time: `validateConfigFile` + # runs `otelcol validate` in the nix sandbox, where systemd's + # `StateDirectory=` has never run and the path does not exist + # yet — without it the validator refuses with "directory must + # exist". + file_storage = { + directory = "/var/lib/opentelemetry-collector"; + create_directory = true; + }; + } + // lib.optionalAttrs senderAuth { + ${authName} = { + client_id = hiveClient; + # A real key, measured against this collector version rather + # than assumed — with a deliberate typo rejected in the same + # run, so "accepted" is distinguishable from "ignores + # everything". Keeps the secret out of nix entirely: the + # collector opens the file itself. + client_secret_file = "\${env:CREDENTIALS_DIRECTORY}/${credName}"; + token_url = "${toString autheliaCfg.url}/api/oidc/token"; + # ⚠️ THE AUDIENCE HAS TO BE REQUESTED, not merely granted. + # Registering it on the client only makes it permissible; a + # token minted without asking carries `aud: []` and every + # receiver refuses it — with a config that reads perfectly at + # both ends. Measured against authelia 4.39.20. + endpoint_params.audience = hiveClient; + }; }; receivers = { @@ -676,24 +699,6 @@ in }; } ]; - } - // lib.optionalAttrs senderAuth { - extensions.${authName} = { - client_id = hiveClient; - # A real key, measured against this collector version rather - # than assumed — with a deliberate typo rejected in the same - # run, so "accepted" is distinguishable from "ignores - # everything". Keeps the secret out of nix entirely: the - # collector opens the file itself. - client_secret_file = "\${env:CREDENTIALS_DIRECTORY}/${credName}"; - token_url = "${toString autheliaCfg.url}/api/oidc/token"; - # ⚠️ THE AUDIENCE HAS TO BE REQUESTED, not merely granted. - # Registering it on the client only makes it permissible; a - # token minted without asking carries `aud: []` and every - # receiver refuses it — with a config that reads perfectly at - # both ends. Measured against authelia 4.39.20. - endpoint_params.audience = hiveClient; - }; }; }; diff --git a/nix/module-eval/hive-otel.nix b/nix/module-eval/hive-otel.nix index f773cc88..1f291b4a 100644 --- a/nix/module-eval/hive-otel.nix +++ b/nix/module-eval/hive-otel.nix @@ -111,6 +111,16 @@ let !(builtins.elem "deltatocumulative" hiveOtelPipelines.logs.processors) && builtins.elem "deltatocumulative" hiveOtelPipelines.metrics.processors; } + { + # `otelcol validate` refuses a `service.extensions` entry with no + # definition, and that runs when the unit is built, not in this suite. + # `hiveOtel` authenticates upstream, so both extensions are listed; + # the length clause keeps the check from passing on an empty list. + name = "the hive collector defines every extension its service lists"; + ok = + lib.length hiveOtelSettings.service.extensions == 2 + && lib.all (e: hiveOtelSettings.extensions ? ${e}) hiveOtelSettings.service.extensions; + } { # The counters that say telemetry is being LOST — refused, failed, # queue depth — are served on loopback and reach no store unless