mirror of
https://github.com/openai/codex.git
synced 2026-11-14 06:48:26 +00:00
fix: introduce AbsolutePathBuf and resolve relative paths in config.toml (#7796)
This PR attempts to solve two problems by introducing a `AbsolutePathBuf` type with a special deserializer: - `AbsolutePathBuf` attempts to be a generally useful abstraction, as it ensures, by constructing, that it represents a value that is an absolute, normalized path, which is a stronger guarantee than an arbitrary `PathBuf`. - Values in `config.toml` that can be either an absolute or relative path should be resolved against the folder containing the `config.toml` in the relative path case. This PR makes this easy to support: the main cost is ensuring `AbsolutePathBufGuard` is used inside `deserialize_config_toml_with_base()`. While `AbsolutePathBufGuard` may seem slightly distasteful because it relies on thread-local storage, this seems much cleaner to me than using than my various experiments with https://docs.rs/serde/latest/serde/de/trait.DeserializeSeed.html. Further, since the `deserialize()` method from the `Deserialize` trait is not async, we do not really have to worry about the deserialization work being spread across multiple threads in a way that would interfere with `AbsolutePathBufGuard`. To start, this PR introduces the use of `AbsolutePathBuf` in `OtelTlsConfig`. Note how this simplifies `otel_provider.rs` because it no longer requires `settings.codex_home` to be threaded through. Furthermore, this sets us up better for a world where multiple `config.toml` files from different folders could be loaded and then merged together, as the absolutifying of the paths must be done against the correct parent folder.
This commit is contained in:
1 parent
0c8828c5e2
commit
fa4cac1e6b
10 files changed
+246
-62
No files matched your search
@@ -1,6 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OtelSettings {
|
||||
pub environment: String,
|
||||
@@ -20,9 +22,9 @@ pub enum OtelHttpProtocol {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OtelTlsConfig {
|
||||
pub ca_certificate: Option<PathBuf>,
|
||||
pub client_certificate: Option<PathBuf>,
|
||||
pub client_private_key: Option<PathBuf>,
|
||||
pub ca_certificate: Option<AbsolutePathBuf>,
|
||||
pub client_certificate: Option<AbsolutePathBuf>,
|
||||
pub client_private_key: Option<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::config::OtelExporter;
|
||||
use crate::config::OtelHttpProtocol;
|
||||
use crate::config::OtelSettings;
|
||||
use crate::config::OtelTlsConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use http::Uri;
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry_otlp::LogExporter;
|
||||
@@ -25,7 +26,6 @@ use std::error::Error;
|
||||
use std::fs;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::{self};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tonic::metadata::MetadataMap;
|
||||
@@ -85,12 +85,7 @@ impl OtelProvider {
|
||||
.assume_http2(true);
|
||||
|
||||
let tls_config = match tls.as_ref() {
|
||||
Some(tls) => build_grpc_tls_config(
|
||||
endpoint,
|
||||
base_tls_config,
|
||||
tls,
|
||||
settings.codex_home.as_path(),
|
||||
)?,
|
||||
Some(tls) => build_grpc_tls_config(endpoint, base_tls_config, tls)?,
|
||||
None => base_tls_config,
|
||||
};
|
||||
|
||||
@@ -123,7 +118,7 @@ impl OtelProvider {
|
||||
.with_headers(headers.clone());
|
||||
|
||||
if let Some(tls) = tls.as_ref() {
|
||||
let client = build_http_client(tls, settings.codex_home.as_path())?;
|
||||
let client = build_http_client(tls)?;
|
||||
exporter_builder = exporter_builder.with_http_client(client);
|
||||
}
|
||||
|
||||
@@ -149,7 +144,6 @@ fn build_grpc_tls_config(
|
||||
endpoint: &str,
|
||||
tls_config: ClientTlsConfig,
|
||||
tls: &OtelTlsConfig,
|
||||
codex_home: &Path,
|
||||
) -> Result<ClientTlsConfig, Box<dyn Error>> {
|
||||
let uri: Uri = endpoint.parse()?;
|
||||
let host = uri.host().ok_or_else(|| {
|
||||
@@ -161,14 +155,14 @@ fn build_grpc_tls_config(
|
||||
let mut config = tls_config.domain_name(host.to_owned());
|
||||
|
||||
if let Some(path) = tls.ca_certificate.as_ref() {
|
||||
let (pem, _) = read_bytes(codex_home, path)?;
|
||||
let (pem, _) = read_bytes(path)?;
|
||||
config = config.ca_certificate(TonicCertificate::from_pem(pem));
|
||||
}
|
||||
|
||||
match (&tls.client_certificate, &tls.client_private_key) {
|
||||
(Some(cert_path), Some(key_path)) => {
|
||||
let (cert_pem, _) = read_bytes(codex_home, cert_path)?;
|
||||
let (key_pem, _) = read_bytes(codex_home, key_path)?;
|
||||
let (cert_pem, _) = read_bytes(cert_path)?;
|
||||
let (key_pem, _) = read_bytes(key_path)?;
|
||||
config = config.identity(TonicIdentity::from_pem(cert_pem, key_pem));
|
||||
}
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
@@ -188,24 +182,20 @@ fn build_grpc_tls_config(
|
||||
/// `opentelemetry_sdk` `BatchLogProcessor` spawns a dedicated OS thread that uses
|
||||
/// `futures_executor::block_on()` rather than tokio. When the async reqwest client's
|
||||
/// timeout calls `tokio::time::sleep()`, it panics with "no reactor running".
|
||||
fn build_http_client(
|
||||
tls: &OtelTlsConfig,
|
||||
codex_home: &Path,
|
||||
) -> Result<reqwest::blocking::Client, Box<dyn Error>> {
|
||||
fn build_http_client(tls: &OtelTlsConfig) -> Result<reqwest::blocking::Client, Box<dyn Error>> {
|
||||
// Wrap in block_in_place because reqwest::blocking::Client creates its own
|
||||
// internal tokio runtime, which would panic if built directly from an async context.
|
||||
tokio::task::block_in_place(|| build_http_client_inner(tls, codex_home))
|
||||
tokio::task::block_in_place(|| build_http_client_inner(tls))
|
||||
}
|
||||
|
||||
fn build_http_client_inner(
|
||||
tls: &OtelTlsConfig,
|
||||
codex_home: &Path,
|
||||
) -> Result<reqwest::blocking::Client, Box<dyn Error>> {
|
||||
let mut builder = reqwest::blocking::Client::builder()
|
||||
.timeout(resolve_otlp_timeout(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT));
|
||||
|
||||
if let Some(path) = tls.ca_certificate.as_ref() {
|
||||
let (pem, location) = read_bytes(codex_home, path)?;
|
||||
let (pem, location) = read_bytes(path)?;
|
||||
let certificate = ReqwestCertificate::from_pem(pem.as_slice()).map_err(|error| {
|
||||
config_error(format!(
|
||||
"failed to parse certificate {}: {error}",
|
||||
@@ -220,8 +210,8 @@ fn build_http_client_inner(
|
||||
|
||||
match (&tls.client_certificate, &tls.client_private_key) {
|
||||
(Some(cert_path), Some(key_path)) => {
|
||||
let (mut cert_pem, cert_location) = read_bytes(codex_home, cert_path)?;
|
||||
let (key_pem, key_location) = read_bytes(codex_home, key_path)?;
|
||||
let (mut cert_pem, cert_location) = read_bytes(cert_path)?;
|
||||
let (key_pem, key_location) = read_bytes(key_path)?;
|
||||
cert_pem.extend_from_slice(key_pem.as_slice());
|
||||
let identity = ReqwestIdentity::from_pem(cert_pem.as_slice()).map_err(|error| {
|
||||
config_error(format!(
|
||||
@@ -264,25 +254,16 @@ fn read_timeout_env(var: &str) -> Option<Duration> {
|
||||
Some(Duration::from_millis(parsed as u64))
|
||||
}
|
||||
|
||||
fn read_bytes(base: &Path, provided: &PathBuf) -> Result<(Vec<u8>, PathBuf), Box<dyn Error>> {
|
||||
let resolved = resolve_config_path(base, provided);
|
||||
match fs::read(&resolved) {
|
||||
Ok(bytes) => Ok((bytes, resolved)),
|
||||
fn read_bytes(path: &AbsolutePathBuf) -> Result<(Vec<u8>, PathBuf), Box<dyn Error>> {
|
||||
match fs::read(path) {
|
||||
Ok(bytes) => Ok((bytes, path.to_path_buf())),
|
||||
Err(error) => Err(Box::new(io::Error::new(
|
||||
error.kind(),
|
||||
format!("failed to read {}: {error}", resolved.display()),
|
||||
format!("failed to read {}: {error}", path.display()),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_config_path(base: &Path, provided: &PathBuf) -> PathBuf {
|
||||
if provided.is_absolute() {
|
||||
provided.clone()
|
||||
} else {
|
||||
base.join(provided)
|
||||
}
|
||||
}
|
||||
|
||||
fn config_error(message: impl Into<String>) -> Box<dyn Error> {
|
||||
Box::new(io::Error::new(ErrorKind::InvalidData, message.into()))
|
||||
}
|
||||
Reference in new issue
Block a user