Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tokio = { version = "1.36.0", optional = true }

# Other
axum = "0.8.1"
eyre = { version = "0.6.12", optional = true }
serde = { version = "1", features = ["derive"] }
thiserror = "2.0.11"
tower = "0.5.2"
Expand All @@ -71,7 +72,7 @@ tokio = { version = "1.43.0", features = ["macros"] }
default = ["alloy", "rustls"]
alloy = ["dep:alloy"]
aws = ["alloy", "alloy?/signer-aws", "dep:async-trait", "dep:aws-config", "dep:aws-sdk-kms"]
perms = ["dep:oauth2", "dep:tokio", "dep:reqwest", "dep:signet-tx-cache"]
perms = ["dep:eyre", "dep:oauth2", "dep:tokio", "dep:reqwest", "dep:signet-tx-cache"]
pylon = ["perms", "alloy/kzg"]
block_watcher = ["dep:tokio"]
rustls = ["dep:rustls", "rustls/aws-lc-rs"]
Expand Down
22 changes: 20 additions & 2 deletions examples/ajj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,29 @@
//! http://localhost:8080/rpc
//! ```
use ajj::Router;
use init4_bin_base::init4;
use init4_bin_base::{
utils::{from_env::FromEnv, metrics::MetricsConfig, tracing::TracingConfig},
Init4Config,
};

#[derive(Debug, FromEnv)]
struct Config {
tracing: TracingConfig,
metrics: MetricsConfig,
}

impl Init4Config for Config {
fn tracing(&self) -> &TracingConfig {
&self.tracing
}
fn metrics(&self) -> &MetricsConfig {
&self.metrics
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = init4();
let _config_and_guard = init4_bin_base::init::<Config>()?;

let router = Router::<()>::new()
.route("helloWorld", || async {
Expand Down
25 changes: 22 additions & 3 deletions examples/build-helper.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
use init4_bin_base::init4;
use init4_bin_base::{
utils::{from_env::FromEnv, metrics::MetricsConfig, tracing::TracingConfig},
Init4Config,
};
use std::sync::{atomic::AtomicBool, Arc};

fn main() {
#[derive(Debug, FromEnv)]
struct Config {
tracing: TracingConfig,
metrics: MetricsConfig,
}

impl Init4Config for Config {
fn tracing(&self) -> &TracingConfig {
&self.tracing
}
fn metrics(&self) -> &MetricsConfig {
&self.metrics
}
}

fn main() -> eyre::Result<()> {
let term: Arc<AtomicBool> = Default::default();

let _ = signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term));

init4();
let _config_and_guard = init4_bin_base::init::<Config>()?;
Ok(())
}
29 changes: 24 additions & 5 deletions examples/otlp-export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,41 @@
//!
//! It can be killed via sigint or sigterm

use eyre::WrapErr;
use init4_bin_base::{
deps::tracing::{info, info_span},
init4,
utils::{from_env::FromEnv, metrics::MetricsConfig, tracing::TracingConfig},
Init4Config,
};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};

#[derive(Debug, FromEnv)]
struct Config {
tracing: TracingConfig,
metrics: MetricsConfig,
}

impl Init4Config for Config {
fn tracing(&self) -> &TracingConfig {
&self.tracing
}
fn metrics(&self) -> &MetricsConfig {
&self.metrics
}
}

#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
async fn main() -> eyre::Result<()> {
let term: Arc<AtomicBool> = Default::default();
signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term))?;
signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term))?;
signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term))
.wrap_err("failed to register SIGTERM hook")?;
signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term))
.wrap_err("failed to register SIGINT hook")?;

let _guard = init4();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@init4samwise can you list all of the downstream callers of this init4 function in init4tech repos?

let _config_and_guard = init4_bin_base::init::<Config>()?;
let mut counter = 0;
let _outer = info_span!("outer span").entered();

Expand Down
79 changes: 78 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
#![deny(unused_must_use, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

use crate::utils::{
from_env::{FromEnv, FromEnvErr},
metrics::MetricsConfig,
otlp::OtelGuard,
tracing::TracingConfig,
};

#[cfg(feature = "perms")]
/// Permissioning and authorization utilities for Signet builders.
pub mod perms;
Expand Down Expand Up @@ -85,7 +92,8 @@ pub mod deps {
///
/// [`init_tracing`]: utils::tracing::init_tracing
/// [`init_metrics`]: utils::metrics::init_metrics
pub fn init4() -> Option<utils::otlp::OtelGuard> {
#[deprecated(since = "0.18.0-rc.11", note = "use `init` instead")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goodbye cute little name 🥹

pub fn init4() -> Option<OtelGuard> {
let guard = utils::tracing::init_tracing();
utils::metrics::init_metrics();

Expand All @@ -96,3 +104,72 @@ pub fn init4() -> Option<utils::otlp::OtelGuard> {

guard
}

/// Trait for config types that can be used with [`init`].
///
/// Implementors must provide access to [`TracingConfig`] and [`MetricsConfig`],
/// and must be loadable from the environment via [`FromEnv`].
///
/// # Example
///
/// ```ignore
/// #[derive(Debug, FromEnv)]
/// pub struct MyConfig {
/// pub tracing: TracingConfig,
/// pub metrics: MetricsConfig,
/// #[from_env(var = "MY_THING", desc = "some app-specific value")]
/// pub my_thing: String,
/// }
///
/// impl Init4Config for MyConfig {
/// fn tracing(&self) -> &TracingConfig { &self.tracing }
/// fn metrics(&self) -> &MetricsConfig { &self.metrics }
/// }
/// ```
pub trait Init4Config: FromEnv {
/// Get the tracing configuration.
fn tracing(&self) -> &TracingConfig;
/// Get the metrics configuration.
fn metrics(&self) -> &MetricsConfig;
}

/// The result of [`init`]: the loaded config and an optional OTLP guard.
///
/// The [`OtelGuard`] (if present) must be kept alive for the lifetime of the
/// program to ensure the OTLP exporter continues to send data.
#[derive(Debug)]
pub struct ConfigAndGuard<T> {
/// The loaded configuration.
pub config: T,
/// The OTLP guard, if OTLP was enabled.
pub guard: Option<OtelGuard>,
}

/// Load config from the environment and initialize metrics and tracing.
///
/// This will perform the following:
/// - Load `T` from environment variables via [`FromEnv`]
/// - Read tracing configuration from the loaded config
/// - Determine whether to enable OTLP
/// - Install a global tracing subscriber, using the OTLP provider if enabled
/// - Read metrics configuration from the loaded config
/// - Install a global metrics recorder and serve it over HTTP on 0.0.0.0
///
/// See [`init_tracing`] and [`init_metrics`] for more
/// details on specific actions taken and env vars read.
///
/// [`init_tracing`]: utils::tracing::init_tracing
/// [`init_metrics`]: utils::metrics::init_metrics
pub fn init<T: Init4Config>() -> Result<ConfigAndGuard<T>, FromEnvErr> {
let config = T::from_env()?;

let guard = utils::tracing::init_tracing_with_config(config.tracing().clone());
utils::metrics::init_metrics_with_config(*config.metrics());

// This will install the AWS-LC-Rust TLS provider for rustls, if no other
// provider has been installed yet
#[cfg(feature = "rustls")]
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();

Ok(ConfigAndGuard { config, guard })
}
47 changes: 23 additions & 24 deletions src/perms/oauth.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! Service responsible for authenticating with the cache with Oauth tokens.
//! This authenticator periodically fetches a new token every set amount of seconds.
use crate::{deps::tracing::error, utils::from_env::FromEnv};
use core::{error::Error, fmt};
use crate::{
deps::tracing::{debug, warn, Instrument},
utils::from_env::FromEnv,
};
use core::fmt;
use eyre::eyre;
use oauth2::{
basic::{BasicClient, BasicTokenType},
AccessToken, AuthUrl, ClientId, ClientSecret, EmptyExtraTokenFields, EndpointNotSet,
Expand All @@ -12,8 +16,8 @@ use std::{future::IntoFuture, pin::Pin};
use tokio::{
sync::watch::{self, Ref},
task::JoinHandle,
time::MissedTickBehavior,
};
use tracing::{debug, Instrument};

type Token = StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>;

Expand Down Expand Up @@ -88,10 +92,16 @@ impl Authenticator {
.set_auth_uri(AuthUrl::from_url(config.oauth_authenticate_url.clone()))
.set_token_uri(TokenUrl::from_url(config.oauth_token_url.clone()));

// NB: this is MANDATORY
// NB: redirect policy none is MANDATORY
// https://docs.rs/oauth2/latest/oauth2/#security-warning
//
// Disable connection pooling to avoid stale connection errors.
// OAuth refreshes are infrequent (typically every 60s), so idle
// connections are almost always closed by the server or
// intermediary (e.g. Istio envoy) before the next request.
let rq_client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.pool_max_idle_per_host(0)
.build()
.unwrap();

Expand Down Expand Up @@ -159,31 +169,20 @@ impl Authenticator {

/// Create a future that contains the periodic refresh loop.
async fn task_future(self) {
let interval = self.config.oauth_token_refresh_interval;
let duration = tokio::time::Duration::from_secs(self.config.oauth_token_refresh_interval);
let mut interval = tokio::time::interval(duration);
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);

loop {
interval.tick().await;
debug!("Refreshing oauth token");
match self.authenticate().await {
Ok(_) => {
debug!("Successfully refreshed oauth token");
}
Err(err) => {
let mut current = &err as &dyn Error;

// This is a little hacky, but the oauth library nests
// errors quite deeply, so we need to walk the source chain
// to get the full picture.
let mut source_chain = Vec::new();
while let Some(source) = current.source() {
source_chain.push(source.to_string());
current = source;
}
let source_chain = source_chain.join("\n\n Caused by: \n");

error!(%err, %source_chain, "Failed to refresh oauth token");
}
Ok(_) => debug!("Successfully refreshed oauth token"),
Err(error) => warn!(
error = %format!("{:#}", eyre!(error)),
"Failed to refresh oauth token"
),
Comment on lines +172 to +184
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior to wait before authenticating, but we want to maintain previous behavior, which is authenticate then wait. This ensures fast startups for anything gated by authentication.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first tick is immediately ready, so there's no real change in behaviour here.

};
let _sleep = tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await;
}
}

Expand Down
Loading