Skip to content

Commit

Permalink
Simpler
Browse files Browse the repository at this point in the history
  • Loading branch information
Razz4780 committed Feb 6, 2025
1 parent 61b67d8 commit 26fac04
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 85 deletions.
137 changes: 70 additions & 67 deletions mirrord/agent/env/src/checked_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
convert::Infallible,
fmt,
marker::PhantomData,
net::{AddrParseError, IpAddr, SocketAddr},
os::unix::ffi::OsStrExt,
str::{FromStr, Utf8Error},
};
Expand All @@ -11,106 +12,66 @@ use k8s_openapi::api::core::v1::EnvVar;
use thiserror::Error;

/// A representation of an environment variable value.
pub trait EnvRepr {
/// Type of the value, e.g `u32`.
type Value;
pub trait EnvValue: Sized {
/// Error that can occur when producing the value representation.
type IntoReprError;
/// Error that can occur when reading the value from the representation.
type FromReprError;

/// Produces a representation for the given value.
fn into_repr(value: &Self::Value) -> Result<String, Self::IntoReprError>;
fn into_repr(&self) -> Result<String, Self::IntoReprError>;

Check failure on line 22 in mirrord/agent/env/src/checked_env.rs

View workflow job for this annotation

GitHub Actions / lint

methods called `into_*` usually take `self` by value

Check failure on line 22 in mirrord/agent/env/src/checked_env.rs

View workflow job for this annotation

GitHub Actions / macos_tests

methods called `into_*` usually take `self` by value

/// Reads a value from the given representation.
fn from_repr(repr: &[u8]) -> Result<Self::Value, Self::FromReprError>;
fn from_repr(repr: &[u8]) -> Result<Self, Self::FromReprError>;
}

/// Implementation of [`EnvRepr`] that uses [`fmt::Display`] and [`FromStr`] to handle conversions.
pub struct StringRepr<T>(PhantomData<fn() -> T>);
pub trait StoredAsString: fmt::Display + FromStr {}

/// Errors that can occur when reading an environment variable value from the representation using
/// [`StringRepr`] or [`CommaSeparatedRepr`].
#[derive(Error, Debug)]
pub enum ParseEnvError<E> {
Utf8Error(#[source] Utf8Error),
ParseError(#[source] E),
}

impl<T> EnvRepr for StringRepr<T>
where
T: fmt::Display + FromStr,
{
type Value = T;
impl<T: StoredAsString> EnvValue for T {
type IntoReprError = Infallible;
type FromReprError = ParseEnvError<T::Err>;

fn into_repr(value: &T) -> Result<String, Self::IntoReprError> {
Ok(value.to_string())
fn into_repr(&self) -> Result<String, Self::IntoReprError> {
Ok(self.to_string())
}

fn from_repr(repr: &[u8]) -> Result<T, Self::FromReprError> {
fn from_repr(repr: &[u8]) -> Result<Self, Self::FromReprError> {
let as_str = std::str::from_utf8(repr).map_err(ParseEnvError::Utf8Error)?;
as_str.parse().map_err(ParseEnvError::ParseError)
}
}

/// Implementation of [`EnvRepr`] for vectors of values.
///
/// Uses [`fmt::Display`] and [`FromStr`] to handle conversion of each value.
/// Final representation is a comma-separated list.
pub struct CommaSeparatedRepr<T>(PhantomData<fn() -> T>);

impl<T> EnvRepr for CommaSeparatedRepr<T>
where
T: fmt::Display + FromStr,
{
type Value = Vec<T>;
type IntoReprError = Infallible;
type FromReprError = ParseEnvError<T::Err>;

fn from_repr(repr: &[u8]) -> Result<Self::Value, Self::FromReprError> {
let as_str = std::str::from_utf8(repr).map_err(ParseEnvError::Utf8Error)?;

as_str
.split(',')
.map(|item| item.parse::<T>())
.collect::<Result<Vec<_>, _>>()
.map_err(ParseEnvError::ParseError)
}

fn into_repr(value: &Self::Value) -> Result<String, Self::IntoReprError> {
Ok(value
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(","))
}
/// Errors that can occur when reading an environment variable value from the representation using
/// [`StringRepr`] or [`CommaSeparatedRepr`].

Check failure on line 45 in mirrord/agent/env/src/checked_env.rs

View workflow job for this annotation

GitHub Actions / check-rust-docs

unresolved link to `StringRepr`

Check failure on line 45 in mirrord/agent/env/src/checked_env.rs

View workflow job for this annotation

GitHub Actions / check-rust-docs

unresolved link to `CommaSeparatedRepr`
#[derive(Error, Debug)]
pub enum ParseEnvError<E> {
Utf8Error(#[source] Utf8Error),
ParseError(#[source] E),
}

/// An environment variable with strict value type checking.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct CheckedEnv<R: EnvRepr> {
pub struct CheckedEnv<V: EnvValue> {
/// Name of the variable.
pub name: &'static str,
repr: PhantomData<fn() -> R>,
value_type: PhantomData<fn() -> V>,
}

impl<R: EnvRepr> CheckedEnv<R> {
impl<V: EnvValue> CheckedEnv<V> {
/// Creates a new instance.
///
/// All instances should be kept together in [`super::envs`].
pub(crate) const fn new(name: &'static str) -> Self {
Self {
name,
repr: PhantomData,
value_type: PhantomData,
}
}

/// Produces an [`EnvVar`] spec, using the given value.
#[cfg(feature = "k8s-openapi")]
pub fn try_as_k8s_spec(self, value: &R::Value) -> Result<EnvVar, R::IntoReprError> {
let repr = R::into_repr(value)?;
pub fn try_as_k8s_spec(self, value: &V) -> Result<EnvVar, V::IntoReprError> {
let repr = V::into_repr(value)?;

Ok(EnvVar {
name: self.name.into(),
Expand All @@ -120,28 +81,28 @@ impl<R: EnvRepr> CheckedEnv<R> {
}

/// Reads this variable's value from the process environment.
pub fn try_from_env(self) -> Result<Option<R::Value>, R::FromReprError> {
pub fn try_from_env(self) -> Result<Option<V>, V::FromReprError> {
match std::env::var_os(self.name) {
Some(repr) => {
let value = R::from_repr(repr.as_bytes())?;
let value = V::from_repr(repr.as_bytes())?;
Ok(Some(value))
}
None => Ok(None),
}
}
}

impl<R: EnvRepr<IntoReprError = Infallible>> CheckedEnv<R> {
impl<V: EnvValue<IntoReprError = Infallible>> CheckedEnv<V> {
/// Convenience method for producing an [`EnvVar`] spec
/// when producing value representation cannot fail.
#[cfg(feature = "k8s-openapi")]
pub fn as_k8s_spec(self, value: &R::Value) -> EnvVar {
pub fn as_k8s_spec(self, value: &V) -> EnvVar {
let Ok(env) = self.try_as_k8s_spec(value);
env
}
}

impl<R: EnvRepr<Value = bool>> CheckedEnv<R> {
impl CheckedEnv<bool> {
/// Convenience method for checking whether this variable is set.
///
/// For variables with [`bool`] values.
Expand All @@ -153,14 +114,56 @@ impl<R: EnvRepr<Value = bool>> CheckedEnv<R> {
}
}

impl<R: EnvRepr> fmt::Debug for CheckedEnv<R> {
impl<V: EnvValue> fmt::Debug for CheckedEnv<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name)
}
}

impl<R: EnvRepr> fmt::Display for CheckedEnv<R> {
impl<V: EnvValue> fmt::Display for CheckedEnv<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name)
}
}

impl StoredAsString for bool {}

impl StoredAsString for u32 {}

impl StoredAsString for SocketAddr {}

impl EnvValue for String {
type IntoReprError = Infallible;
type FromReprError = Utf8Error;

fn into_repr(&self) -> Result<String, Self::IntoReprError> {
Ok(self.clone())
}

fn from_repr(repr: &[u8]) -> Result<Self, Self::FromReprError> {
std::str::from_utf8(repr).map(From::from)
}
}

impl EnvValue for Vec<IpAddr> {
type IntoReprError = Infallible;
type FromReprError = ParseEnvError<AddrParseError>;

fn into_repr(&self) -> Result<String, Self::IntoReprError> {
Ok(self
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(","))
}

fn from_repr(repr: &[u8]) -> Result<Self, Self::FromReprError> {
let as_str = std::str::from_utf8(repr).map_err(ParseEnvError::Utf8Error)?;

as_str
.split(',')
.map(|item| item.parse::<IpAddr>())
.collect::<Result<Vec<_>, _>>()
.map_err(ParseEnvError::ParseError)
}
}
32 changes: 14 additions & 18 deletions mirrord/agent/env/src/envs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,50 @@
use std::net::{IpAddr, SocketAddr};

use crate::checked_env::{CheckedEnv, CommaSeparatedRepr, StringRepr};
use crate::checked_env::CheckedEnv;

/// Used to pass operator's x509 certificate to the agent.
///
/// This way the agent can be sure that it only accepts TLS connections coming from the exact
/// operator that spawned it.
pub const OPERATOR_CERT: CheckedEnv<StringRepr<String>> =
CheckedEnv::new("AGENT_OPERATOR_CERT_ENV");
pub const OPERATOR_CERT: CheckedEnv<String> = CheckedEnv::new("AGENT_OPERATOR_CERT_ENV");

/// Determines a network interface for mirroring.
pub const NETWORK_INTERFACE: CheckedEnv<StringRepr<String>> =
CheckedEnv::new("AGENT_NETWORK_INTERFACE_ENV");
pub const NETWORK_INTERFACE: CheckedEnv<String> = CheckedEnv::new("AGENT_NETWORK_INTERFACE_ENV");

/// Enables Prometheus metrics export point and sets its address.
pub const METRICS: CheckedEnv<StringRepr<SocketAddr>> = CheckedEnv::new("MIRRORD_AGENT_METRICS");
pub const METRICS: CheckedEnv<SocketAddr> = CheckedEnv::new("MIRRORD_AGENT_METRICS");

/// Used to inform the agent that mesh is present in the target pod.
pub const IN_SERVICE_MESH: CheckedEnv<StringRepr<bool>> =
CheckedEnv::new("MIRRORD_AGENT_IN_SERVICE_MESH");
pub const IN_SERVICE_MESH: CheckedEnv<bool> = CheckedEnv::new("MIRRORD_AGENT_IN_SERVICE_MESH");

/// Used to inform the agent that istio cni mesh is present in the target pod.
pub const ISTIO_CNI: CheckedEnv<StringRepr<bool>> = CheckedEnv::new("MIRRORD_AGENT_ISTIO_CNI");
pub const ISTIO_CNI: CheckedEnv<bool> = CheckedEnv::new("MIRRORD_AGENT_ISTIO_CNI");

/// Instructs the agent to flush connections when adding new iptables rules.
pub const STEALER_FLUSH_CONNECTIONS: CheckedEnv<StringRepr<bool>> =
pub const STEALER_FLUSH_CONNECTIONS: CheckedEnv<bool> =
CheckedEnv::new("MIRRORD_AGENT_STEALER_FLUSH_CONNECTIONS");

/// Instructs the agent to use `iptables-nft` instead of `iptables-legacy` for manipulating
/// iptables.
pub const NFTABLES: CheckedEnv<StringRepr<bool>> = CheckedEnv::new("MIRRORD_AGENT_NFTABLES");
pub const NFTABLES: CheckedEnv<bool> = CheckedEnv::new("MIRRORD_AGENT_NFTABLES");

/// Instructs the agent to produces logs in JSON format.
pub const JSON_LOG: CheckedEnv<StringRepr<bool>> = CheckedEnv::new("MIRRORD_AGENT_JSON_LOG");
pub const JSON_LOG: CheckedEnv<bool> = CheckedEnv::new("MIRRORD_AGENT_JSON_LOG");

/// Enables IPv6 support in the agent.
pub const IPV6: CheckedEnv<StringRepr<bool>> = CheckedEnv::new("AGENT_IPV6_ENV");
pub const IPV6: CheckedEnv<bool> = CheckedEnv::new("AGENT_IPV6_ENV");

/// Sets a hard timeout on DNS queries.
pub const DNS_TIMEOUT: CheckedEnv<StringRepr<u32>> = CheckedEnv::new("MIRRORD_AGENT_DNS_TIMEOUT");
pub const DNS_TIMEOUT: CheckedEnv<u32> = CheckedEnv::new("MIRRORD_AGENT_DNS_TIMEOUT");

/// Sets a hard limit on DNS query attempts.
pub const DNS_ATTEMPTS: CheckedEnv<StringRepr<u32>> = CheckedEnv::new("MIRRORD_AGENT_DNS_ATTEMPTS");
pub const DNS_ATTEMPTS: CheckedEnv<u32> = CheckedEnv::new("MIRRORD_AGENT_DNS_ATTEMPTS");

/// This is currently not used in the agent.
pub const POD_IPS: CheckedEnv<CommaSeparatedRepr<IpAddr>> =
CheckedEnv::new("MIRRORD_AGENT_POD_IPS");
pub const POD_IPS: CheckedEnv<Vec<IpAddr>> = CheckedEnv::new("MIRRORD_AGENT_POD_IPS");

/// Sets agent log level.
///
/// Should follow `tracing`` format, e.g `mirrord=trace`.
pub const LOG_LEVEL: CheckedEnv<StringRepr<String>> = CheckedEnv::new("RUST_LOG");
pub const LOG_LEVEL: CheckedEnv<String> = CheckedEnv::new("RUST_LOG");

0 comments on commit 26fac04

Please sign in to comment.