move otel_http_client from swarm-queue-client into swarm-controller
This commit is contained in:
parent
361396cab8
commit
f3be08f6b6
9 changed files with 52 additions and 70 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -4575,8 +4575,10 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64",
|
||||||
|
"bytes",
|
||||||
"forgejo-api",
|
"forgejo-api",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hive-jobq",
|
"hive-jobq",
|
||||||
|
|
@ -4584,6 +4586,7 @@ dependencies = [
|
||||||
"hive-jobq-wire",
|
"hive-jobq-wire",
|
||||||
"hive-types",
|
"hive-types",
|
||||||
"hmac 0.13.0",
|
"hmac 0.13.0",
|
||||||
|
"http",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-http",
|
"opentelemetry-http",
|
||||||
"opentelemetry-otlp",
|
"opentelemetry-otlp",
|
||||||
|
|
@ -4629,10 +4632,6 @@ name = "swarm-queue-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-nats",
|
"async-nats",
|
||||||
"async-trait",
|
|
||||||
"bytes",
|
|
||||||
"http",
|
|
||||||
"opentelemetry-http",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,8 @@ opentelemetry-otlp = { version = "0.32", default-features = false, features = [
|
||||||
# The trait an OTLP exporter's HTTP transport is built on
|
# The trait an OTLP exporter's HTTP transport is built on
|
||||||
# (`opentelemetry_otlp::WithHttpConfig::with_http_client`) — pulled in
|
# (`opentelemetry_otlp::WithHttpConfig::with_http_client`) — pulled in
|
||||||
# directly (not just transitively via `opentelemetry-otlp` above) by
|
# directly (not just transitively via `opentelemetry-otlp` above) by
|
||||||
# `swarm-queue-client`'s `otel-auth` feature, which implements the trait
|
# `swarm-controller`'s own `otel_http_client` module, which implements the
|
||||||
# itself rather than using the crate's own blanket `reqwest`/
|
# trait itself rather than using the crate's own blanket `reqwest`/
|
||||||
# `reqwest-blocking` impls (this workspace's implementation needs to mint
|
# `reqwest-blocking` impls (this workspace's implementation needs to mint
|
||||||
# a fresh bearer token per request, which no blanket impl can do). No
|
# a fresh bearer token per request, which no blanket impl can do). No
|
||||||
# extra cargo features requested here for exactly that reason — the
|
# extra cargo features requested here for exactly that reason — the
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
|
||||||
|
|
||||||
/// Type-erases a caller's concrete [`HttpClient`] impl so [`spawn_exporter`]
|
/// Type-erases a caller's concrete [`HttpClient`] impl so [`spawn_exporter`]
|
||||||
/// can accept "any client, or none" without this crate depending on any one
|
/// can accept "any client, or none" without this crate depending on any one
|
||||||
/// caller's concrete type (`swarm-controller`'s
|
/// caller's concrete type (`swarm-controller`'s own
|
||||||
/// `swarm_queue_client::otel_http_client::AuthenticatedHttpClient` today,
|
/// `otel_http_client::AuthenticatedHttpClient` today, conceivably something
|
||||||
/// conceivably something else from a future caller) — the same genericity
|
/// else from a future caller) — the same genericity
|
||||||
/// this crate already has over `N`/`R`, applied to the one other caller-
|
/// this crate already has over `N`/`R`, applied to the one other caller-
|
||||||
/// supplied thing it touches.
|
/// supplied thing it touches.
|
||||||
///
|
///
|
||||||
|
|
@ -84,8 +84,8 @@ const DEFAULT_INTERVAL: Duration = Duration::from_mins(1);
|
||||||
/// before this parameter existed. `Some(client)` routes every export
|
/// before this parameter existed. `Some(client)` routes every export
|
||||||
/// through that [`HttpClient`] instead, for a caller whose destination
|
/// through that [`HttpClient`] instead, for a caller whose destination
|
||||||
/// checks a bearer token per request rather than a static header
|
/// checks a bearer token per request rather than a static header
|
||||||
/// (`swarm-controller`'s case — see `swarm_queue_client::otel_http_client`'s
|
/// (`swarm-controller`'s case — see its own `otel_http_client` module doc
|
||||||
/// module doc for why a static header doesn't fit an expiring token).
|
/// for why a static header doesn't fit an expiring token).
|
||||||
pub fn spawn_exporter<N, R>(
|
pub fn spawn_exporter<N, R>(
|
||||||
jobq: Arc<Mutex<hive_jobq::scheduler::Scheduler<N, R>>>,
|
jobq: Arc<Mutex<hive_jobq::scheduler::Scheduler<N, R>>>,
|
||||||
service_name: &str,
|
service_name: &str,
|
||||||
|
|
|
||||||
|
|
@ -84,16 +84,20 @@ swarm-authelia-bridge-sock.workspace = true
|
||||||
# creation config are shared with the hive that writes it, so this end does
|
# creation config are shared with the hive that writes it, so this end does
|
||||||
# not get to declare them privately.
|
# not get to declare them privately.
|
||||||
#
|
#
|
||||||
# `otel-auth` for `vcs_metrics`'s `AuthenticatedHttpClient` — see that
|
swarm-queue-client = { workspace = true, features = ["kv"] }
|
||||||
# feature's own comment in `swarm-queue-client`'s Cargo.toml.
|
# `otel_http_client.rs`'s `AuthenticatedHttpClient` — an
|
||||||
swarm-queue-client = { workspace = true, features = ["kv", "otel-auth"] }
|
# `opentelemetry_http::HttpClient` impl authenticated with this crate's own
|
||||||
# The `HttpClient` trait itself, so `main.rs` can name
|
# `swarm-queue-client` identity. Lives in this crate rather than
|
||||||
|
# `swarm-queue-client` (mara's own call during review) since this daemon is
|
||||||
|
# its only caller; see that module's doc for the full rationale. `opentelemetry-http`
|
||||||
|
# is also needed directly (not just transitively) so `main.rs` can name
|
||||||
# `Box<dyn opentelemetry_http::HttpClient>` when handing
|
# `Box<dyn opentelemetry_http::HttpClient>` when handing
|
||||||
# `vcs_metrics::authenticated_http_client()`'s output to
|
# `vcs_metrics::authenticated_http_client()`'s output to
|
||||||
# `hive_jobq_metrics::spawn_exporter` — a transitive dependency (via
|
# `hive_jobq_metrics::spawn_exporter`.
|
||||||
# `swarm-queue-client`'s `otel-auth` feature above) isn't enough to `use`
|
|
||||||
# it directly, Cargo requires a direct entry for that.
|
|
||||||
opentelemetry-http.workspace = true
|
opentelemetry-http.workspace = true
|
||||||
|
async-trait.workspace = true
|
||||||
|
bytes.workspace = true
|
||||||
|
http.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ use utoipa_axum::{router::OpenApiRouter, routes};
|
||||||
mod auth;
|
mod auth;
|
||||||
mod config_pr;
|
mod config_pr;
|
||||||
mod forge;
|
mod forge;
|
||||||
|
mod otel_http_client;
|
||||||
mod status;
|
mod status;
|
||||||
mod vcs_metrics;
|
mod vcs_metrics;
|
||||||
mod webhook;
|
mod webhook;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
//! An [`opentelemetry_http::HttpClient`] that authenticates every request
|
//! An [`opentelemetry_http::HttpClient`] that authenticates every request
|
||||||
//! with a fresh, audience-scoped bearer token minted from this crate's own
|
//! with a fresh, audience-scoped bearer token minted from this daemon's own
|
||||||
//! [`QueueConfig`] identity — "one identity per principal" applied to a
|
//! [`swarm_queue_client::QueueConfig`] identity — "one identity per
|
||||||
//! third destination (the queue connection and `swarm-authelia-bridge`'s
|
//! principal" applied to a third destination (the queue connection and
|
||||||
//! client, [`crate::mint_token_for`], are the other two).
|
//! `auth.rs`'s bridge client, `swarm_queue_client::mint_token_for`, are the
|
||||||
|
//! other two).
|
||||||
|
//!
|
||||||
|
//! Lives here, not in `swarm-queue-client` (mara's own call during review:
|
||||||
|
//! it does not belong in the queue-connect crate) — this daemon is the only
|
||||||
|
//! caller, and the module only *reuses* that crate's
|
||||||
|
//! `QueueConfig`/`mint_token_for_blocking`, it doesn't extend the
|
||||||
|
//! queue-connect contract those exist for.
|
||||||
//!
|
//!
|
||||||
//! Exists for exactly one shape of caller: an OTLP metric exporter whose
|
//! Exists for exactly one shape of caller: an OTLP metric exporter whose
|
||||||
//! `PeriodicReader` drives export from a background thread with **no tokio
|
//! `PeriodicReader` drives export from a background thread with **no tokio
|
||||||
//! reactor** (`opentelemetry-otlp`'s `reqwest-blocking-client` feature is
|
//! reactor** (`opentelemetry-otlp`'s `reqwest-blocking-client` feature is
|
||||||
//! chosen everywhere in this workspace for that reason — see
|
//! chosen everywhere in this workspace for that reason — see
|
||||||
//! `swarm-controller::vcs_metrics`'s module doc). That constraint is why
|
//! `vcs_metrics`'s module doc). That constraint is why this mints via
|
||||||
//! this mints via [`crate::mint_token_for_blocking`] rather than the async
|
//! [`swarm_queue_client::mint_token_for_blocking`] rather than the async
|
||||||
//! [`crate::mint_token_for`]: the latter needs a runtime to `.await` on,
|
//! `swarm_queue_client::mint_token_for`: the latter needs a runtime to
|
||||||
//! which the calling thread does not have.
|
//! `.await` on, which the calling thread does not have.
|
||||||
//!
|
//!
|
||||||
//! Per-request, not per-tick or cached: `opentelemetry-otlp` 0.32 exposes
|
//! Per-request, not per-tick or cached: `opentelemetry-otlp` 0.32 exposes
|
||||||
//! `WithHttpConfig::with_http_client`, a seam for a caller-supplied
|
//! `WithHttpConfig::with_http_client`, a seam for a caller-supplied
|
||||||
|
|
@ -25,8 +32,7 @@ use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
use opentelemetry_http::{HttpClient, HttpError};
|
use opentelemetry_http::{HttpClient, HttpError};
|
||||||
|
use swarm_queue_client::{QueueConfig, mint_token_for_blocking};
|
||||||
use crate::{QueueConfig, mint_token_for_blocking};
|
|
||||||
|
|
||||||
/// See the module doc. Wraps a plain `reqwest::blocking::Client` — deliberately
|
/// See the module doc. Wraps a plain `reqwest::blocking::Client` — deliberately
|
||||||
/// not `opentelemetry_http`'s own blanket impl for that type (behind its
|
/// not `opentelemetry_http`'s own blanket impl for that type (behind its
|
||||||
|
|
@ -37,9 +43,9 @@ pub struct AuthenticatedHttpClient {
|
||||||
inner: reqwest::blocking::Client,
|
inner: reqwest::blocking::Client,
|
||||||
cfg: QueueConfig,
|
cfg: QueueConfig,
|
||||||
/// The audience requested at the token endpoint — see
|
/// The audience requested at the token endpoint — see
|
||||||
/// [`crate::mint_token_for_blocking`]'s own doc (via
|
/// [`swarm_queue_client::mint_token_for_blocking`]'s own doc (via
|
||||||
/// [`crate::mint_token_for`]'s) for why this has to be asked for
|
/// `swarm_queue_client::mint_token_for`'s) for why this has to be asked
|
||||||
/// explicitly rather than left to whatever the identity's default is.
|
/// for explicitly rather than left to whatever the identity's default is.
|
||||||
audience: String,
|
audience: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,8 +118,8 @@ fn build_provider(interval: Duration) -> Result<SdkMeterProvider> {
|
||||||
// `.with_http_client(..)`: unlike the hive tier's own exporters, this
|
// `.with_http_client(..)`: unlike the hive tier's own exporters, this
|
||||||
// one crosses a real trust boundary (the swarm-tier `otlp/swarm`
|
// one crosses a real trust boundary (the swarm-tier `otlp/swarm`
|
||||||
// receiver checks a bearer token's audience, see
|
// receiver checks a bearer token's audience, see
|
||||||
// `swarm_queue_client::otel_http_client`'s module doc) — a plain
|
// `crate::otel_http_client`'s module doc) — a plain endpoint-only
|
||||||
// endpoint-only client would be refused at the receiver.
|
// client would be refused at the receiver.
|
||||||
let exporter = MetricExporter::builder()
|
let exporter = MetricExporter::builder()
|
||||||
.with_http()
|
.with_http()
|
||||||
.with_protocol(Protocol::HttpJson)
|
.with_protocol(Protocol::HttpJson)
|
||||||
|
|
@ -135,8 +135,8 @@ fn build_provider(interval: Duration) -> Result<SdkMeterProvider> {
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the [`swarm_queue_client::otel_http_client::AuthenticatedHttpClient`]
|
/// Build the [`crate::otel_http_client::AuthenticatedHttpClient`] this
|
||||||
/// this exporter pushes through.
|
/// exporter pushes through.
|
||||||
///
|
///
|
||||||
/// The queue identity (`SWARM_CONTROLLER_OIDC_*`) is read fresh here rather
|
/// The queue identity (`SWARM_CONTROLLER_OIDC_*`) is read fresh here rather
|
||||||
/// than threaded in from a caller — by the time this runs, [`endpoint`] has
|
/// than threaded in from a caller — by the time this runs, [`endpoint`] has
|
||||||
|
|
@ -160,8 +160,8 @@ fn build_provider(interval: Duration) -> Result<SdkMeterProvider> {
|
||||||
/// this unit for the same reason the forge client needs no special
|
/// this unit for the same reason the forge client needs no special
|
||||||
/// handling either) — a second, narrower trust bundle here would just be
|
/// handling either) — a second, narrower trust bundle here would just be
|
||||||
/// the same fact stated twice.
|
/// the same fact stated twice.
|
||||||
pub(crate) fn authenticated_http_client()
|
pub(crate) fn authenticated_http_client() -> Result<crate::otel_http_client::AuthenticatedHttpClient>
|
||||||
-> Result<swarm_queue_client::otel_http_client::AuthenticatedHttpClient> {
|
{
|
||||||
let cfg = swarm_queue_client::QueueConfig::from_env("SWARM_CONTROLLER")
|
let cfg = swarm_queue_client::QueueConfig::from_env("SWARM_CONTROLLER")
|
||||||
.context("reading the queue identity this daemon authenticates its OTLP push with")?
|
.context("reading the queue identity this daemon authenticates its OTLP push with")?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
|
|
@ -175,13 +175,11 @@ pub(crate) fn authenticated_http_client()
|
||||||
"SWARM_CONTROLLER_OTEL_AUDIENCE is unset, but OTEL_EXPORTER_OTLP_ENDPOINT is — the nix \
|
"SWARM_CONTROLLER_OTEL_AUDIENCE is unset, but OTEL_EXPORTER_OTLP_ENDPOINT is — the nix \
|
||||||
module sets both together",
|
module sets both together",
|
||||||
)?;
|
)?;
|
||||||
Ok(
|
Ok(crate::otel_http_client::AuthenticatedHttpClient::new(
|
||||||
swarm_queue_client::otel_http_client::AuthenticatedHttpClient::new(
|
reqwest::blocking::Client::new(),
|
||||||
reqwest::blocking::Client::new(),
|
cfg,
|
||||||
cfg,
|
audience,
|
||||||
audience,
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `service.name = swarm-controller` plus whatever the operator set in
|
/// `service.name = swarm-controller` plus whatever the operator set in
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,6 @@ kv = ["async-nats/kv"]
|
||||||
# `cargo check -p swarm-nats-auth` — no `kv` anywhere in that build —
|
# `cargo check -p swarm-nats-auth` — no `kv` anywhere in that build —
|
||||||
# surfaced it as `cannot find jetstream in async_nats`).
|
# surfaced it as `cannot find jetstream in async_nats`).
|
||||||
notices = ["async-nats/jetstream"]
|
notices = ["async-nats/jetstream"]
|
||||||
# OFF by default, same reasoning as `kv`/`notices` above: only a caller
|
|
||||||
# wiring an OTLP exporter's `HttpClient` seam to this crate's identity
|
|
||||||
# (today, `swarm-controller`'s `vcs_metrics`/`hive_jobq_metrics`) needs
|
|
||||||
# `opentelemetry_http`/`async-trait`/`bytes`/`http` pulled in — a plain
|
|
||||||
# queue-connect-only consumer (the auth-callout responder, a hive
|
|
||||||
# publishing its status) has no reason to carry them.
|
|
||||||
otel-auth = [
|
|
||||||
"dep:opentelemetry-http",
|
|
||||||
"dep:async-trait",
|
|
||||||
"dep:bytes",
|
|
||||||
"dep:http",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Bare (no `kv`/`jetstream`) unless a consumer opts into the `kv` feature
|
# Bare (no `kv`/`jetstream`) unless a consumer opts into the `kv` feature
|
||||||
|
|
@ -57,12 +45,6 @@ async-nats.workspace = true
|
||||||
# feature's comment above), and `cargo check -p swarm-queue-client` alone
|
# feature's comment above), and `cargo check -p swarm-queue-client` alone
|
||||||
# must not depend on what else is in the build.
|
# must not depend on what else is in the build.
|
||||||
reqwest = { workspace = true, features = ["blocking"] }
|
reqwest = { workspace = true, features = ["blocking"] }
|
||||||
# All four `optional = true`, gated behind the `otel-auth` feature above —
|
|
||||||
# see that feature's own comment for why.
|
|
||||||
opentelemetry-http = { workspace = true, optional = true }
|
|
||||||
async-trait = { workspace = true, optional = true }
|
|
||||||
bytes = { workspace = true, optional = true }
|
|
||||||
http = { workspace = true, optional = true }
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
# A library, so its errors are a matchable enum rather than an opaque
|
# A library, so its errors are a matchable enum rather than an opaque
|
||||||
|
|
|
||||||
|
|
@ -164,14 +164,6 @@ pub fn chain(error: &dyn std::error::Error) -> String {
|
||||||
/// which is the disagreement this module exists to prevent.
|
/// which is the disagreement this module exists to prevent.
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
/// An [`opentelemetry_http::HttpClient`] impl that authenticates every
|
|
||||||
/// request with a fresh token from this crate's identity — see the
|
|
||||||
/// module's own doc for the full rationale. Behind the `otel-auth`
|
|
||||||
/// feature, same reasoning as `status`/`kv` above: only a caller wiring an
|
|
||||||
/// OTLP exporter to this identity needs the extra dependencies it pulls in.
|
|
||||||
#[cfg(feature = "otel-auth")]
|
|
||||||
pub mod otel_http_client;
|
|
||||||
|
|
||||||
/// The subject the swarm controller publishes on when the hive-wide knowledge
|
/// The subject the swarm controller publishes on when the hive-wide knowledge
|
||||||
/// repository has changed. One writer, many readers — every hive subscribes.
|
/// repository has changed. One writer, many readers — every hive subscribes.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue