Skip to content
Draft
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
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ rust-version = "1.91.0"
version = "0.1.0"

[workspace.dependencies]
anyhow = "1.0.98"
arc-swap = "1.7"
async-compression = { version = "0.4", default-features = false }
async-stream = "0.3"
Expand Down Expand Up @@ -105,6 +104,7 @@ zstd = "0.13"
# Local workspace crates
binary-cache = { path = "subprojects/crates/binary-cache" }
db = { path = "subprojects/crates/db" }
error-context = { path = "subprojects/crates/error-context" }
hydra-proto = { path = "subprojects/crates/proto" }
hydra-tracing = { path = "subprojects/crates/tracing" }
nix-support = { path = "subprojects/crates/nix-support" }
Expand Down
1 change: 1 addition & 0 deletions subprojects/crates/binary-cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ harmonia-utils-signature.workspace = true
nix-utils.workspace = true

[dev-dependencies]
anyhow = "1"
hydra-tracing.workspace = true
tempfile = "3.23.0"
2 changes: 1 addition & 1 deletion subprojects/crates/binary-cache/examples/download_file.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use binary_cache::S3BinaryCacheClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let _tracing_guard = hydra_tracing::init()?;
let client = S3BinaryCacheClient::new(
"s3://store?region=unknown&endpoint=http://localhost:9000&scheme=http&write-nar-listing=1&ls-compression=br&log-compression=br&profile=local_nix_store".parse()?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use binary_cache::S3BinaryCacheClient;
use nix_utils::BaseStore as _;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let _tracing_guard = hydra_tracing::init()?;
let store = nix_utils::LocalStore::init();

Expand Down
4 changes: 2 additions & 2 deletions subprojects/crates/binary-cache/examples/simple_presigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use binary_cache::{PresignedUploadClient, S3BinaryCacheClient, path_to_narinfo};
use nix_utils::BaseStore as _;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let now = std::time::Instant::now();

let _tracing_guard = hydra_tracing::init()?;
Expand Down Expand Up @@ -51,7 +51,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
client
.upload_narinfo_after_presigned_upload(&store, narinfo)
.await?;
Ok::<(), Box<dyn std::error::Error>>(())
Ok::<(), anyhow::Error>(())
}
})
.buffered(10);
Expand Down
2 changes: 1 addition & 1 deletion subprojects/crates/binary-cache/examples/upload_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use binary_cache::S3BinaryCacheClient;
use nix_utils::BaseStore as _;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let now = std::time::Instant::now();

let _tracing_guard = hydra_tracing::init()?;
Expand Down
2 changes: 1 addition & 1 deletion subprojects/crates/binary-cache/examples/upload_logs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use binary_cache::S3BinaryCacheClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let _tracing_guard = hydra_tracing::init()?;
let client = S3BinaryCacheClient::new(
"s3://store?region=unknown&endpoint=http://localhost:9000&scheme=http&write-nar-listing=1&ls-compression=br&log-compression=br&profile=local_nix_store".parse()?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use binary_cache::S3BinaryCacheClient;
use harmonia_store_derivation::realisation::DrvOutput;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let _tracing_guard = hydra_tracing::init()?;
let _local = nix_utils::LocalStore::init();
let client = S3BinaryCacheClient::new(
Expand Down
11 changes: 11 additions & 0 deletions subprojects/crates/error-context/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "error-context"
version = "0.1.0"
edition = "2024"
# This is much more trivial and generic than the rest of Hydra. And I
# would like to make it easier to pull out and put on crates.io if we
# see fit.
license = "MIT"
rust-version.workspace = true

[dependencies]
93 changes: 93 additions & 0 deletions subprojects/crates/error-context/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Lightweight error context wrapper — like `anyhow::context()` but
//! preserving the original typed error.

/// An error wrapped with zero or more layers of context.
#[derive(Debug)]
pub enum WithContext<E> {
/// The original error, no context added.
Root(E),

/// A context message wrapping an inner `WithContext`.
Context {
context: String,
source: Box<WithContext<E>>,
},
}

impl<E> WithContext<E> {
/// Get a reference to the innermost (root) error.
pub fn inner(&self) -> &E {
match self {
Self::Root(e) => e,
Self::Context { source, .. } => source.inner(),
}
}

/// Consume and return the innermost (root) error.
pub fn into_inner(self) -> E {
match self {
Self::Root(e) => e,
Self::Context { source, .. } => source.into_inner(),
}
}
}

impl<E: std::fmt::Display> std::fmt::Display for WithContext<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Root(e) => e.fmt(f),
Self::Context { context, source } => write!(f, "{context}: {source}"),
}
}
}

impl<E: std::error::Error + 'static> std::error::Error for WithContext<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Root(e) => e.source(),
Self::Context { source, .. } => Some(source.inner()),
}
}
}

/// Extension trait adding `.context("...")` to any `Result`.
pub trait ResultExt<T, E> {
/// Wrap the error with additional context.
fn context(self, msg: impl Into<String>) -> Result<T, WithContext<E>>;

/// Wrap the error with lazily-computed context.
fn with_context(self, f: impl FnOnce() -> String) -> Result<T, WithContext<E>>;
}

impl<T, E> ResultExt<T, E> for Result<T, E> {
fn context(self, msg: impl Into<String>) -> Result<T, WithContext<E>> {
self.map_err(|e| WithContext::Context {
context: msg.into(),
source: Box::new(WithContext::Root(e)),
})
}

fn with_context(self, f: impl FnOnce() -> String) -> Result<T, WithContext<E>> {
self.map_err(|e| WithContext::Context {
context: f(),
source: Box::new(WithContext::Root(e)),
})
}
}

/// Allow adding more context to an already-wrapped error.
impl<T, E> ResultExt<T, E> for Result<T, WithContext<E>> {
fn context(self, msg: impl Into<String>) -> Result<T, WithContext<E>> {
self.map_err(|e| WithContext::Context {
context: msg.into(),
source: Box::new(e),
})
}

fn with_context(self, f: impl FnOnce() -> String) -> Result<T, WithContext<E>> {
self.map_err(|e| WithContext::Context {
context: f(),
source: Box::new(e),
})
}
}
2 changes: 1 addition & 1 deletion subprojects/crates/nix-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ mod tests {
let fs = FilesystemOperations {
real_store_dir: store_dir.to_path().to_owned(),
};
let bp = Box::pin(parse_build_product(&store_dir, &fs, line)).await;
let bp = parse_build_product(&store_dir, &fs, line).await;
assert!(bp.is_none());
}

Expand Down
3 changes: 3 additions & 0 deletions subprojects/crates/nix-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ harmonia-store-path-info.workspace = true
harmonia-utils-hash.workspace = true
harmonia-utils-signature.workspace = true

[dev-dependencies]
anyhow = "1"

[build-dependencies]
cxx-build.workspace = true
pkg-config.workspace = true
2 changes: 1 addition & 1 deletion subprojects/crates/nix-utils/examples/is_valid_path.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use nix_utils::{self, BaseStore as _};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let store = nix_utils::LocalStore::init();
let store_dir = nix_utils::get_store_dir();
println!(
Expand Down
2 changes: 1 addition & 1 deletion subprojects/crates/nix-utils/examples/path_infos.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use nix_utils::BaseStore as _;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
let local = nix_utils::LocalStore::init();

let p1 = "ihl4ya67glh9815v1lanyqph0p7hdzfb-hdf5-cpp-1.14.6-bin"
Expand Down
2 changes: 1 addition & 1 deletion subprojects/crates/nix-utils/examples/stream_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use tokio::io::AsyncReadExt;
use tokio_util::io::StreamReader;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn main() -> anyhow::Result<()> {
// Create a stream from an iterator.
let stream = tokio_stream::iter(vec![
tokio::io::Result::Ok(Bytes::from_static(&[0, 1, 2, 3])),
Expand Down
14 changes: 7 additions & 7 deletions subprojects/hydra-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ rust-version.workspace = true
sd-notify.workspace = true
tracing.workspace = true

anyhow.workspace = true
clap = { workspace = true, features = [ "derive" ] }
fs-err = { workspace = true, features = [ "tokio" ] }
hashbrown.workspace = true
parking_lot.workspace = true
thiserror.workspace = true
uuid = { workspace = true, features = [ "v4" ] }
clap = { workspace = true, features = [ "derive" ] }
error-context.workspace = true
fs-err = { workspace = true, features = [ "tokio" ] }
hashbrown.workspace = true
parking_lot.workspace = true
thiserror.workspace = true
uuid = { workspace = true, features = [ "v4" ] }

async-compression = { workspace = true, features = [ "tokio", "zstd" ] }
async-stream.workspace = true
Expand Down
39 changes: 29 additions & 10 deletions subprojects/hydra-builder/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
use clap::Parser;

/// Errors from reading builder configuration (mTLS certs, etc.).
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("missing config option: {0}")]
MissingOption(&'static str),

#[error(transparent)]
Io(#[from] std::io::Error),

#[error(
"mTLS configured improperly, please pass all options: \
server_root_ca_cert_path, client_cert_path, client_key_path and domain_name"
)]
MtlsIncomplete,
}

#[derive(Parser, Debug)]
#[clap(
author,
Expand Down Expand Up @@ -117,27 +133,30 @@ impl Cli {
#[tracing::instrument(skip(self), err)]
pub async fn get_mtls(
&self,
) -> anyhow::Result<(
tonic::transport::Certificate,
tonic::transport::Identity,
String,
)> {
) -> Result<
(
tonic::transport::Certificate,
tonic::transport::Identity,
String,
),
ConfigError,
> {
let server_root_ca_cert_path = self
.server_root_ca_cert_path
.as_deref()
.ok_or_else(|| anyhow::anyhow!("server_root_ca_cert_path not provided"))?;
.ok_or(ConfigError::MissingOption("server_root_ca_cert_path"))?;
let client_cert_path = self
.client_cert_path
.as_deref()
.ok_or_else(|| anyhow::anyhow!("client_cert_path not provided"))?;
.ok_or(ConfigError::MissingOption("client_cert_path"))?;
let client_key_path = self
.client_key_path
.as_deref()
.ok_or_else(|| anyhow::anyhow!("client_key_path not provided"))?;
.ok_or(ConfigError::MissingOption("client_key_path"))?;
let domain_name = self
.domain_name
.as_deref()
.ok_or_else(|| anyhow::anyhow!("domain_name not provided"))?;
.ok_or(ConfigError::MissingOption("domain_name"))?;

let server_root_ca_cert = fs_err::tokio::read_to_string(server_root_ca_cert_path).await?;
let server_root_ca_cert = tonic::transport::Certificate::from_pem(server_root_ca_cert);
Expand All @@ -150,7 +169,7 @@ impl Cli {
}

#[tracing::instrument(skip(self), err)]
pub async fn get_authorization_token(&self) -> anyhow::Result<Option<String>> {
pub async fn get_authorization_token(&self) -> Result<Option<String>, ConfigError> {
if let Some(path) = &self.authorization_file {
Ok(Some(
fs_err::tokio::read_to_string(path)
Expand Down
Loading