diff --git a/Cargo.toml b/Cargo.toml index 6616417..179b6db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "init4-bin-base" description = "Internal utilities for binaries produced by the init4 team" keywords = ["init4", "bin", "base"] -version = "0.1.4" +version = "0.2.0" edition = "2021" rust-version = "1.81" authors = ["init4", "James Prestwich"] diff --git a/src/perms/builders.rs b/src/perms/builders.rs index fcf717e..ff2caa3 100644 --- a/src/perms/builders.rs +++ b/src/perms/builders.rs @@ -10,7 +10,7 @@ use crate::{ perms::{SlotAuthzConfig, SlotAuthzConfigError}, utils::{ calc::SlotCalculator, - from_env::{FromEnv, FromEnvErr, FromEnvVar}, + from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar}, }, }; @@ -173,6 +173,18 @@ impl Builders { impl FromEnv for Builders { type Error = BuilderConfigError; + fn inventory() -> Vec<&'static EnvItemInfo> { + let mut v = vec![ + &EnvItemInfo { + var: BUILDERS, + description: "A comma-separated list of UUIDs representing the builders that are allowed to perform actions.", + optional: false, + }, + ]; + v.extend(SlotAuthzConfig::inventory()); + v + } + fn from_env() -> Result> { let s = String::from_env_var(BUILDERS) .map_err(FromEnvErr::infallible_into::)?; diff --git a/src/perms/config.rs b/src/perms/config.rs index 38f82f7..6785332 100644 --- a/src/perms/config.rs +++ b/src/perms/config.rs @@ -1,6 +1,6 @@ use crate::utils::{ calc::{SlotCalcEnvError, SlotCalculator}, - from_env::{FromEnv, FromEnvErr, FromEnvVar}, + from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar}, }; use core::num; @@ -76,6 +76,21 @@ impl SlotAuthzConfig { impl FromEnv for SlotAuthzConfig { type Error = SlotAuthzConfigError; + fn inventory() -> Vec<&'static EnvItemInfo> { + let mut v = vec![ + &EnvItemInfo { + var: BLOCK_QUERY_CUTOFF, + description: "The block query cutoff time in seconds. This is the slot second after which requests will not be serviced. E.g. a value of 1 means that requests will not be serviced for the last second of any given slot.", + optional: false, + }, &EnvItemInfo { + var: BLOCK_QUERY_START, + description: "The block query start time in seconds. This is the slot second before which requests will not be serviced. E.g. a value of 1 means that requests will not be serviced for the first second of any given slot.", + optional: false, + }]; + v.extend(SlotCalculator::inventory()); + v + } + fn from_env() -> Result> { let calc = SlotCalculator::from_env().map_err(FromEnvErr::from)?; let block_query_cutoff = u8::from_env_var(BLOCK_QUERY_CUTOFF) diff --git a/src/utils/calc.rs b/src/utils/calc.rs index 51175ff..d04e87d 100644 --- a/src/utils/calc.rs +++ b/src/utils/calc.rs @@ -1,6 +1,8 @@ use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar}; use core::num; +use super::from_env::EnvItemInfo; + // Env vars pub(crate) const START_TIMESTAMP: &str = "START_TIMESTAMP"; pub(crate) const SLOT_OFFSET: &str = "SLOT_OFFSET"; @@ -125,6 +127,26 @@ impl SlotCalculator { impl FromEnv for SlotCalculator { type Error = SlotCalcEnvError; + fn inventory() -> Vec<&'static EnvItemInfo> { + vec![ + &EnvItemInfo { + var: START_TIMESTAMP, + description: "The start timestamp of the chain in seconds", + optional: false, + }, + &EnvItemInfo { + var: SLOT_OFFSET, + description: "The slot offset of the chain in seconds", + optional: false, + }, + &EnvItemInfo { + var: SLOT_DURATION, + description: "The slot duration of the chain in seconds", + optional: false, + }, + ] + } + fn from_env() -> Result> { let start_timestamp = u64::from_env_var(START_TIMESTAMP) .map_err(|e| e.map(SlotCalcEnvError::StartTimestamp))?; diff --git a/src/utils/from_env.rs b/src/utils/from_env.rs index 1f32741..50a8eb5 100644 --- a/src/utils/from_env.rs +++ b/src/utils/from_env.rs @@ -1,5 +1,18 @@ use std::{convert::Infallible, env::VarError, num::ParseIntError, str::FromStr}; +/// Details about an environment variable. This is used to generate +/// documentation for the environment variables and by the [`FromEnv`] trait to +/// check if necessary environment variables are present. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EnvItemInfo { + /// The environment variable name. + pub var: &'static str, + /// A description of the environment variable function in the CFG. + pub description: &'static str, + /// Whether the environment variable is optional or not. + pub optional: bool, +} + /// Error type for loading from the environment. See the [`FromEnv`] trait for /// more information. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] @@ -93,6 +106,33 @@ pub trait FromEnv: core::fmt::Debug + Sized + 'static { /// Error type produced when loading from the environment. type Error: core::error::Error; + /// Get the required environment variable names for this type. + /// + /// ## Note + /// + /// This MUST include the environment variable names for all fields in the + /// struct, including optional vars. + fn inventory() -> Vec<&'static EnvItemInfo>; + + /// Get a list of missing environment variables. + /// + /// This will check all environment variables in the inventory, and return + /// a list of those that are non-optional and missing. This is useful for + /// reporting missing environment variables. + fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> { + let mut missing = Vec::new(); + for var in Self::inventory() { + if std::env::var(var.var).is_err() && !var.optional { + missing.push(var); + } + } + if missing.is_empty() { + Ok(()) + } else { + Err(missing) + } + } + /// Load from the environment. fn from_env() -> Result>; } diff --git a/src/utils/metrics.rs b/src/utils/metrics.rs index b5f4fa7..8e084ea 100644 --- a/src/utils/metrics.rs +++ b/src/utils/metrics.rs @@ -1,6 +1,8 @@ use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar}; use metrics_exporter_prometheus::PrometheusBuilder; +use super::from_env::EnvItemInfo; + /// Metrics port env var const METRICS_PORT: &str = "METRICS_PORT"; @@ -40,6 +42,14 @@ impl From for MetricsConfig { impl FromEnv for MetricsConfig { type Error = std::num::ParseIntError; + fn inventory() -> Vec<&'static EnvItemInfo> { + vec![&EnvItemInfo { + var: METRICS_PORT, + description: "Port on which to serve metrics, u16, defaults to 9000", + optional: true, + }] + } + fn from_env() -> Result> { match u16::from_env_var(METRICS_PORT).map(Self::from) { Ok(cfg) => Ok(cfg), diff --git a/src/utils/otlp.rs b/src/utils/otlp.rs index 154fb92..1a6cda4 100644 --- a/src/utils/otlp.rs +++ b/src/utils/otlp.rs @@ -1,4 +1,4 @@ -use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar}; +use crate::utils::from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar}; use opentelemetry::{trace::TracerProvider, KeyValue}; use opentelemetry_sdk::trace::SdkTracerProvider; use opentelemetry_sdk::Resource; @@ -110,6 +110,32 @@ pub struct OtelConfig { impl FromEnv for OtelConfig { type Error = url::ParseError; + fn inventory() -> Vec<&'static EnvItemInfo> { + vec![ + &EnvItemInfo { + var: OTEL_ENDPOINT, + description: + "OTLP endpoint to send traces to, a url. If missing, disables OTLP exporting.", + optional: true, + }, + &EnvItemInfo { + var: OTEL_LEVEL, + description: "OTLP level to export, defaults to DEBUG. Permissible values are: TRACE, DEBUG, INFO, WARN, ERROR, OFF", + optional: true, + }, + &EnvItemInfo { + var: OTEL_TIMEOUT, + description: "OTLP timeout in milliseconds", + optional: true, + }, + &EnvItemInfo { + var: OTEL_ENVIRONMENT, + description: "OTLP environment name, a string", + optional: true, + }, + ] + } + fn from_env() -> Result> { // load endpoint from env. ignore empty values (shortcut return None), parse, and print the error if any using inspect_err let endpoint = Url::from_env_var(OTEL_ENDPOINT).inspect_err(|e| eprintln!("{e}"))?;