Watch
0
0
Fork
You've already forked hyperhive
0

swarm-otel: the upstream export is a swarm-otel deploy option

The external export is a setting of the swarm collector, so it moves to
services.hyperhive.deploy.swarm-otel.upstream. Renamed:
- otel.endpoint -> upstream.endpoint
- otel.protocol -> upstream.protocol
- otel.headersCredential -> upstream.headersCredential
- otel.collector.upstreamHeaderName -> upstream.headerName
The unreleased otel.upstreamSignals is declared as upstream.signals.

Hosts keep working through the rename warnings. Eval test covers the old
paths reaching the new options. Docs and stale comments point at the
new paths.
This commit is contained in:
atlas 2026-10-21 10:25:26 +02:00
commit 41a7974b9e
7 changed files with 206 additions and 144 deletions

View file

@ -8,8 +8,9 @@
# collector (./swarm-otel.nix). It holds no credential and picks no
# destination: an agent's samples cross a hive boundary exactly once, and
# what happens after that is the swarm's decision, not a hive's. The
# upstream options declared below describe that far end and are read one
# tier up — they stay here because they mean what they have always meant.
# upstream export is a swarm-collector deploy option,
# `services.hyperhive.deploy.swarm-otel.upstream` (./swarm-otel.nix); the
# old `otel.*` paths forward to it below.
{
pkgs,
lib,
@ -56,6 +57,22 @@ in
hostUnit = true;
enable = runsHive;
})
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "otel" "endpoint" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "upstream" "endpoint" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "otel" "protocol" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "upstream" "protocol" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "otel" "headersCredential" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "upstream" "headersCredential" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "otel" "collector" "upstreamHeaderName" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "upstream" "headerName" ]
)
(lib.mkRemovedOptionModule [ "services" "hyperhive" "otel" "enable" ] ''
Every hive runs its own telemetry collector, and every agent exports to
it by default, so there is no switch. Remove this definition.
@ -67,115 +84,6 @@ in
];
options.services.hyperhive.otel = {
endpoint = lib.mkOption {
type = lib.types.str;
default = "";
example = "https://collector.example.com/otel";
description = ''
Upstream OTLP endpoint: where telemetry ultimately goes, after it
has left the swarm.
Read by the swarm's collector
({option}`services.hyperhive.deploy.swarm-otel.enable`), which is the only
tier that holds the upstream credential. An agent is handed the
*first* hop instead — this hive's own collector — so this value is
never given to a container.
Optional. Leave it empty and the swarm's own metrics store
({option}`services.hyperhive.deploy.victoriametrics.enable`) is the
destination; that is a complete deployment, not a degraded one.
Set both and telemetry goes to both.
'';
};
upstreamSignals = lib.mkOption {
type = lib.types.listOf (
lib.types.enum [
"metrics"
"logs"
]
);
default = [
"metrics"
"logs"
];
example = [ "metrics" ];
description = ''
Which signals the swarm's collector sends to
{option}`services.hyperhive.otel.endpoint`. The swarm's own metrics
and log stores always receive everything, whatever this lists.
Use it when the upstream does not accept a signal, for example one
that answers 404 on `/v1/logs`: list only `"metrics"` and the logs
pipelines stop exporting upstream. An empty list defines no upstream
exporter at all. No pipeline exports traces, so they have no entry.
Has no effect while {option}`services.hyperhive.otel.endpoint` is empty.
'';
};
protocol = lib.mkOption {
type = lib.types.enum [
"http/protobuf"
"http/json"
"grpc"
];
default = "http/protobuf";
description = ''
OTLP wire protocol for the **upstream** link, honoured by the
swarm collector's exporter.
Not what agents speak: their first hop is this hive's collector,
whose OTLP/HTTP receiver takes protobuf whatever the upstream
wants (see `hive-c0re/environment.nix`).
'';
};
headersCredential = lib.mkOption {
# `str`, not `path`: a `path`-typed relative literal is hash-copied
# into the world-readable nix store at eval time, defeating the
# point. Keep it a string + require an absolute runtime path so the
# secret is only ever read from disk by systemd at start.
type = lib.types.nullOr lib.types.str;
default = null;
example = "/run/secrets/otel-headers";
description = ''
Absolute path to an operator-provided secret file holding the
upstream auth header as `NAME=value` (e.g.
`Authorization=Bearer <token>`).
**Only the swarm's collector reads this** — the one tier that
talks to the upstream. It arrives as an `EnvironmentFile`, so the
value is never read by nix, never copied into the store or the
generated config, and never passed in argv; and it reaches
neither an agent container nor this hive's own collector, which
is the point of the tiers existing. Must be absolute.
Leave null if the upstream needs no auth header; the collector
then sends none rather than an empty one.
'';
};
collector.upstreamHeaderName = lib.mkOption {
type = lib.types.str;
default = "Authorization";
description = ''
Name of the HTTP header the collector sends upstream, whose
*value* comes from `headersCredential`.
The name is here and the value is not, and that split is forced
rather than chosen: the collector models exporter headers as a
static map, so rendering them means nix reading the value — the
one thing `headersCredential` being a path exists to prevent.
A name is public, a value is not.
⇒ exactly one header is expressible this way. A credential
carrying several (`a=1,b=2`) would be read as a single value,
which is why the shape is a named header rather than an opaque
blob: a second header has to be *declared*, not smuggled.
'';
};
collector.enable = lib.mkOption {
type = lib.types.bool;
readOnly = true;