mirror of
https://github.com/zhaofengli/attic.git
synced 2025-03-16 21:38:21 +00:00
server: Attach tracing context to errors
This commit is contained in:
parent
27836028f4
commit
c04aff7c48
17 changed files with 200 additions and 108 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -233,6 +233,7 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
"xdg",
|
"xdg",
|
||||||
|
@ -3609,6 +3610,16 @@ dependencies = [
|
||||||
"valuable",
|
"valuable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-error"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||||
|
dependencies = [
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-futures"
|
name = "tracing-futures"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -3630,6 +3641,16 @@ dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-serde"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
@ -3640,12 +3661,15 @@ dependencies = [
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
|
"tracing-serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -5,7 +5,3 @@ members = [
|
||||||
"client",
|
"client",
|
||||||
"server",
|
"server",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
opt-level = 2
|
|
||||||
incremental = true
|
|
||||||
|
|
|
@ -73,12 +73,15 @@
|
||||||
|
|
||||||
rustfmt clippy
|
rustfmt clippy
|
||||||
cargo-expand cargo-outdated cargo-edit
|
cargo-expand cargo-outdated cargo-edit
|
||||||
|
tokio-console
|
||||||
|
|
||||||
sqlite-interactive
|
sqlite-interactive
|
||||||
|
|
||||||
editorconfig-checker
|
editorconfig-checker
|
||||||
|
|
||||||
flyctl
|
flyctl
|
||||||
|
|
||||||
|
wrk
|
||||||
] ++ (lib.optionals pkgs.stdenv.isLinux [
|
] ++ (lib.optionals pkgs.stdenv.isLinux [
|
||||||
linuxPackages.perf
|
linuxPackages.perf
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -52,11 +52,12 @@ serde_json = "1.0.91"
|
||||||
serde_with = "2.1.0"
|
serde_with = "2.1.0"
|
||||||
tokio-util = { version = "0.7.4", features = [ "io" ] }
|
tokio-util = { version = "0.7.4", features = [ "io" ] }
|
||||||
toml = "0.5.10"
|
toml = "0.5.10"
|
||||||
tower-http = { version = "0.3.5", features = [ "catch-panic" ] }
|
tower-http = { version = "0.3.5", features = [ "catch-panic", "trace" ] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-error = "0.2.0"
|
||||||
|
tracing-subscriber = { version = "0.3.16", features = [ "json" ] }
|
||||||
uuid = { version = "1.2.2", features = ["v4"] }
|
uuid = { version = "1.2.2", features = ["v4"] }
|
||||||
console-subscriber = { version = "0.1.8", optional = true }
|
console-subscriber = "0.1.8"
|
||||||
xdg = "2.4.1"
|
xdg = "2.4.1"
|
||||||
|
|
||||||
[dependencies.async-compression]
|
[dependencies.async-compression]
|
||||||
|
@ -92,6 +93,3 @@ features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"sync",
|
"sync",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
|
||||||
tokio-console = ["dep:console-subscriber"]
|
|
||||||
|
|
|
@ -65,23 +65,17 @@ impl AuthState {
|
||||||
|
|
||||||
d
|
d
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(mut e) => {
|
||||||
if permission.can_discover() {
|
e.set_discovery_permission(permission.can_discover());
|
||||||
return Err(e);
|
return Err(e);
|
||||||
} else {
|
|
||||||
return Err(e.into_no_discovery_permissions());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match f(cache, &mut permission) {
|
match f(cache, &mut permission) {
|
||||||
Ok(t) => Ok(t),
|
Ok(t) => Ok(t),
|
||||||
Err(e) => {
|
Err(mut e) => {
|
||||||
if permission.can_discover() {
|
e.set_discovery_permission(permission.can_discover());
|
||||||
Err(e)
|
Err(e)
|
||||||
} else {
|
|
||||||
Err(e.into_no_discovery_permissions())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use tokio_util::io::ReaderStream;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::database::AtticDatabase;
|
use crate::database::AtticDatabase;
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerResult};
|
||||||
use crate::narinfo::NarInfo;
|
use crate::narinfo::NarInfo;
|
||||||
use crate::nix_manifest;
|
use crate::nix_manifest;
|
||||||
use crate::storage::Download;
|
use crate::storage::Download;
|
||||||
|
@ -101,6 +101,7 @@ async fn get_nix_cache_info(
|
||||||
/// - HEAD `/:cache/{storePathHash}.narinfo`
|
/// - HEAD `/:cache/{storePathHash}.narinfo`
|
||||||
/// - GET `/:cache/{storePathHash}.ls` (not implemented)
|
/// - GET `/:cache/{storePathHash}.ls` (not implemented)
|
||||||
#[instrument(skip_all, fields(cache_name, path))]
|
#[instrument(skip_all, fields(cache_name, path))]
|
||||||
|
#[axum_macros::debug_handler]
|
||||||
async fn get_store_path_info(
|
async fn get_store_path_info(
|
||||||
Extension(state): Extension<State>,
|
Extension(state): Extension<State>,
|
||||||
Extension(req_state): Extension<RequestState>,
|
Extension(req_state): Extension<RequestState>,
|
||||||
|
@ -109,12 +110,12 @@ async fn get_store_path_info(
|
||||||
let components: Vec<&str> = path.splitn(2, '.').collect();
|
let components: Vec<&str> = path.splitn(2, '.').collect();
|
||||||
|
|
||||||
if components.len() != 2 {
|
if components.len() != 2 {
|
||||||
return Err(ServerError::NotFound);
|
return Err(ErrorKind::NotFound.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Other endpoints
|
// TODO: Other endpoints
|
||||||
if components[1] != "narinfo" {
|
if components[1] != "narinfo" {
|
||||||
return Err(ServerError::NotFound);
|
return Err(ErrorKind::NotFound.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let store_path_hash = StorePathHash::new(components[0].to_string())?;
|
let store_path_hash = StorePathHash::new(components[0].to_string())?;
|
||||||
|
@ -162,11 +163,11 @@ async fn get_nar(
|
||||||
let components: Vec<&str> = path.splitn(2, '.').collect();
|
let components: Vec<&str> = path.splitn(2, '.').collect();
|
||||||
|
|
||||||
if components.len() != 2 {
|
if components.len() != 2 {
|
||||||
return Err(ServerError::NotFound);
|
return Err(ErrorKind::NotFound.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if components[1] != "nar" {
|
if components[1] != "nar" {
|
||||||
return Err(ServerError::NotFound);
|
return Err(ErrorKind::NotFound.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let store_path_hash = StorePathHash::new(components[0].to_string())?;
|
let store_path_hash = StorePathHash::new(components[0].to_string())?;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tracing::instrument;
|
||||||
|
|
||||||
use crate::database::entity::cache::{self, Entity as Cache};
|
use crate::database::entity::cache::{self, Entity as Cache};
|
||||||
use crate::database::entity::Json as DbJson;
|
use crate::database::entity::Json as DbJson;
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
use crate::{RequestState, State};
|
use crate::{RequestState, State};
|
||||||
use attic::api::v1::cache_config::{
|
use attic::api::v1::cache_config::{
|
||||||
CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
|
CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
|
||||||
|
@ -115,7 +115,7 @@ pub(crate) async fn configure_cache(
|
||||||
}
|
}
|
||||||
RetentionPeriodConfig::Period(period) => {
|
RetentionPeriodConfig::Period(period) => {
|
||||||
update.retention_period = Set(Some(period.try_into().map_err(|_| {
|
update.retention_period = Set(Some(period.try_into().map_err(|_| {
|
||||||
ServerError::RequestError(anyhow!("Invalid retention period"))
|
ErrorKind::RequestError(anyhow!("Invalid retention period"))
|
||||||
})?));
|
})?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,9 +131,9 @@ pub(crate) async fn configure_cache(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ServerError::RequestError(anyhow!(
|
Err(ErrorKind::RequestError(anyhow!(
|
||||||
"No modifiable fields were set."
|
"No modifiable fields were set."
|
||||||
)))
|
)).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ pub(crate) async fn destroy_cache(
|
||||||
|
|
||||||
if deletion.rows_affected == 0 {
|
if deletion.rows_affected == 0 {
|
||||||
// Someone raced to (soft) delete the cache before us
|
// Someone raced to (soft) delete the cache before us
|
||||||
Err(ServerError::NoSuchCache)
|
Err(ErrorKind::NoSuchCache.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ pub(crate) async fn destroy_cache(
|
||||||
|
|
||||||
if deletion.rows_affected == 0 {
|
if deletion.rows_affected == 0 {
|
||||||
// Someone raced to (soft) delete the cache before us
|
// Someone raced to (soft) delete the cache before us
|
||||||
Err(ServerError::NoSuchCache)
|
Err(ErrorKind::NoSuchCache.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ pub(crate) async fn create_cache(
|
||||||
|
|
||||||
if num_inserted == 0 {
|
if num_inserted == 0 {
|
||||||
// The cache already exists
|
// The cache already exists
|
||||||
Err(ServerError::CacheAlreadyExists)
|
Err(ErrorKind::CacheAlreadyExists.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use tracing::instrument;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::CompressionType;
|
use crate::config::CompressionType;
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
use crate::narinfo::Compression;
|
use crate::narinfo::Compression;
|
||||||
use crate::{RequestState, State};
|
use crate::{RequestState, State};
|
||||||
use attic::api::v1::upload_path::UploadPathNarInfo;
|
use attic::api::v1::upload_path::UploadPathNarInfo;
|
||||||
|
@ -88,7 +88,7 @@ pub(crate) async fn upload_path(
|
||||||
let upload_info: UploadPathNarInfo = {
|
let upload_info: UploadPathNarInfo = {
|
||||||
let header = headers
|
let header = headers
|
||||||
.get("X-Attic-Nar-Info")
|
.get("X-Attic-Nar-Info")
|
||||||
.ok_or_else(|| ServerError::RequestError(anyhow!("X-Attic-Nar-Info must be set")))?;
|
.ok_or_else(|| ErrorKind::RequestError(anyhow!("X-Attic-Nar-Info must be set")))?;
|
||||||
|
|
||||||
serde_json::from_slice(header.as_bytes()).map_err(ServerError::request_error)?
|
serde_json::from_slice(header.as_bytes()).map_err(ServerError::request_error)?
|
||||||
};
|
};
|
||||||
|
@ -147,7 +147,7 @@ async fn upload_path_dedup(
|
||||||
|| *nar_size != upload_info.nar_size
|
|| *nar_size != upload_info.nar_size
|
||||||
|| *nar_size != existing_nar.nar_size as usize
|
|| *nar_size != existing_nar.nar_size as usize
|
||||||
{
|
{
|
||||||
return Err(ServerError::RequestError(anyhow!("Bad NAR Hash or Size")));
|
return Err(ErrorKind::RequestError(anyhow!("Bad NAR Hash or Size")).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally...
|
// Finally...
|
||||||
|
@ -280,7 +280,7 @@ async fn upload_path_new(
|
||||||
let file_hash = Hash::Sha256(file_hash.as_slice().try_into().unwrap());
|
let file_hash = Hash::Sha256(file_hash.as_slice().try_into().unwrap());
|
||||||
|
|
||||||
if nar_hash != upload_info.nar_hash || *nar_size != upload_info.nar_size {
|
if nar_hash != upload_info.nar_hash || *nar_size != upload_info.nar_size {
|
||||||
return Err(ServerError::RequestError(anyhow!("Bad NAR Hash or Size")));
|
return Err(ErrorKind::RequestError(anyhow!("Bad NAR Hash or Size")).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally...
|
// Finally...
|
||||||
|
|
|
@ -12,7 +12,7 @@ use sea_orm::sea_query::{Expr, LockBehavior, LockType, Query, Value};
|
||||||
use sea_orm::{ActiveValue::Set, ConnectionTrait, DatabaseConnection, FromQueryResult};
|
use sea_orm::{ActiveValue::Set, ConnectionTrait, DatabaseConnection, FromQueryResult};
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
use attic::cache::CacheName;
|
use attic::cache::CacheName;
|
||||||
use attic::hash::Hash;
|
use attic::hash::Hash;
|
||||||
use attic::nix_store::StorePathHash;
|
use attic::nix_store::StorePathHash;
|
||||||
|
@ -88,7 +88,7 @@ impl AtticDatabase for DatabaseConnection {
|
||||||
.query_one(stmt)
|
.query_one(stmt)
|
||||||
.await
|
.await
|
||||||
.map_err(ServerError::database_error)?
|
.map_err(ServerError::database_error)?
|
||||||
.ok_or(ServerError::NoSuchObject)?;
|
.ok_or(ErrorKind::NoSuchObject)?;
|
||||||
|
|
||||||
let object = object::Model::from_query_result(&result, SELECT_OBJECT)
|
let object = object::Model::from_query_result(&result, SELECT_OBJECT)
|
||||||
.map_err(ServerError::database_error)?;
|
.map_err(ServerError::database_error)?;
|
||||||
|
@ -107,7 +107,7 @@ impl AtticDatabase for DatabaseConnection {
|
||||||
.one(self)
|
.one(self)
|
||||||
.await
|
.await
|
||||||
.map_err(ServerError::database_error)?
|
.map_err(ServerError::database_error)?
|
||||||
.ok_or(ServerError::NoSuchCache)
|
.ok_or_else(|| ErrorKind::NoSuchCache.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_and_lock_nar(&self, nar_hash: &Hash) -> ServerResult<Option<NarGuard>> {
|
async fn find_and_lock_nar(&self, nar_hash: &Hash) -> ServerResult<Option<NarGuard>> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Error handling.
|
//! Error handling.
|
||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::Error as AnyError;
|
use anyhow::Error as AnyError;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
@ -8,14 +9,28 @@ use axum::response::{IntoResponse, Response};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use displaydoc::Display;
|
use displaydoc::Display;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tracing_error::SpanTrace;
|
||||||
|
|
||||||
use attic::error::AtticError;
|
use attic::error::AtticError;
|
||||||
|
|
||||||
pub type ServerResult<T> = Result<T, ServerError>;
|
pub type ServerResult<T> = Result<T, ServerError>;
|
||||||
|
|
||||||
/// An error.
|
/// A server error.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ServerError {
|
||||||
|
/// The kind of the error.
|
||||||
|
kind: ErrorKind,
|
||||||
|
|
||||||
|
/// Whether the client that caused the error has discovery permissions.
|
||||||
|
discovery_permission: bool,
|
||||||
|
|
||||||
|
/// Context of where the error occurred.
|
||||||
|
context: SpanTrace,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of an error.
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
pub enum ServerError {
|
pub enum ErrorKind {
|
||||||
// Generic responses
|
// Generic responses
|
||||||
/// The URL you requested was not found.
|
/// The URL you requested was not found.
|
||||||
NotFound,
|
NotFound,
|
||||||
|
@ -67,17 +82,82 @@ pub struct ErrorResponse {
|
||||||
|
|
||||||
impl ServerError {
|
impl ServerError {
|
||||||
pub fn database_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
pub fn database_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
||||||
Self::DatabaseError(AnyError::new(error))
|
ErrorKind::DatabaseError(AnyError::new(error)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn storage_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
pub fn storage_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
||||||
Self::StorageError(AnyError::new(error))
|
ErrorKind::StorageError(AnyError::new(error)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
pub fn request_error(error: impl StdError + Send + Sync + 'static) -> Self {
|
||||||
Self::RequestError(AnyError::new(error))
|
ErrorKind::RequestError(AnyError::new(error)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_discovery_permission(&mut self, perm: bool) {
|
||||||
|
self.discovery_permission = perm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ServerError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "{}", self.kind)?;
|
||||||
|
self.context.fmt(f)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorKind> for ServerError {
|
||||||
|
fn from(kind: ErrorKind) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
discovery_permission: true,
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AtticError> for ServerError {
|
||||||
|
fn from(error: AtticError) -> Self {
|
||||||
|
ErrorKind::AtticError(error).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<super::access::Error> for ServerError {
|
||||||
|
fn from(error: super::access::Error) -> Self {
|
||||||
|
ErrorKind::AccessError(error).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for ServerError {}
|
||||||
|
|
||||||
|
impl IntoResponse for ServerError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
// TODO: Better logging control
|
||||||
|
if matches!(self.kind, ErrorKind::DatabaseError(_) | ErrorKind::StorageError(_) | ErrorKind::ManifestSerializationError(_) | ErrorKind::AtticError(_)) {
|
||||||
|
tracing::error!("{}", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kind = if self.discovery_permission {
|
||||||
|
self.kind
|
||||||
|
} else {
|
||||||
|
self.kind.into_no_discovery_permissions()
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: don't sanitize in dev mode
|
||||||
|
let sanitized = kind.into_clients();
|
||||||
|
|
||||||
|
let status_code = sanitized.http_status_code();
|
||||||
|
let error_response = ErrorResponse {
|
||||||
|
code: status_code.as_u16(),
|
||||||
|
message: sanitized.to_string(),
|
||||||
|
error: sanitized.name().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(status_code, Json(error_response)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorKind {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::NotFound => "NotFound",
|
Self::NotFound => "NotFound",
|
||||||
|
@ -99,7 +179,7 @@ impl ServerError {
|
||||||
|
|
||||||
/// Returns a more restricted version of this error for a client without discovery
|
/// Returns a more restricted version of this error for a client without discovery
|
||||||
/// permissions.
|
/// permissions.
|
||||||
pub fn into_no_discovery_permissions(self) -> Self {
|
fn into_no_discovery_permissions(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::NoSuchCache => Self::Unauthorized,
|
Self::NoSuchCache => Self::Unauthorized,
|
||||||
Self::NoSuchObject => Self::Unauthorized,
|
Self::NoSuchObject => Self::Unauthorized,
|
||||||
|
@ -139,38 +219,3 @@ impl ServerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for ServerError {}
|
|
||||||
|
|
||||||
impl From<AtticError> for ServerError {
|
|
||||||
fn from(error: AtticError) -> Self {
|
|
||||||
Self::AtticError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<super::access::Error> for ServerError {
|
|
||||||
fn from(error: super::access::Error) -> Self {
|
|
||||||
Self::AccessError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoResponse for ServerError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
// TODO: Better logging control
|
|
||||||
if matches!(self, Self::DatabaseError(_) | Self::StorageError(_) | Self::ManifestSerializationError(_) | Self::AtticError(_)) {
|
|
||||||
tracing::error!("{:?}", self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: don't sanitize in dev mode
|
|
||||||
let sanitized = self.into_clients();
|
|
||||||
|
|
||||||
let status_code = sanitized.http_status_code();
|
|
||||||
let error_response = ErrorResponse {
|
|
||||||
code: status_code.as_u16(),
|
|
||||||
message: sanitized.to_string(),
|
|
||||||
error: sanitized.name().to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
(status_code, Json(error_response)).into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -39,12 +39,13 @@ use sea_orm::{query::Statement, ConnectionTrait, Database, DatabaseConnection};
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
use tower_http::catch_panic::CatchPanicLayer;
|
use tower_http::catch_panic::CatchPanicLayer;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
use access::http::{apply_auth, AuthState};
|
use access::http::{apply_auth, AuthState};
|
||||||
use attic::cache::CacheName;
|
use attic::cache::CacheName;
|
||||||
use config::{Config, StorageConfig};
|
use config::{Config, StorageConfig};
|
||||||
use database::migration::{Migrator, MigratorTrait};
|
use database::migration::{Migrator, MigratorTrait};
|
||||||
use error::{ServerError, ServerResult};
|
use error::{ErrorKind, ServerError, ServerResult};
|
||||||
use middleware::{init_request_state, restrict_host};
|
use middleware::{init_request_state, restrict_host};
|
||||||
use storage::{LocalBackend, S3Backend, StorageBackend};
|
use storage::{LocalBackend, S3Backend, StorageBackend};
|
||||||
|
|
||||||
|
@ -171,7 +172,7 @@ impl RequestStateInner {
|
||||||
/// The fallback route.
|
/// The fallback route.
|
||||||
#[axum_macros::debug_handler]
|
#[axum_macros::debug_handler]
|
||||||
async fn fallback(_: Uri) -> ServerResult<()> {
|
async fn fallback(_: Uri) -> ServerResult<()> {
|
||||||
Err(ServerError::NotFound)
|
Err(ErrorKind::NotFound.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the API server.
|
/// Runs the API server.
|
||||||
|
@ -194,6 +195,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
|
||||||
.layer(axum::middleware::from_fn(init_request_state))
|
.layer(axum::middleware::from_fn(init_request_state))
|
||||||
.layer(axum::middleware::from_fn(restrict_host))
|
.layer(axum::middleware::from_fn(restrict_host))
|
||||||
.layer(Extension(state.clone()))
|
.layer(Extension(state.clone()))
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
.layer(CatchPanicLayer::new());
|
.layer(CatchPanicLayer::new());
|
||||||
|
|
||||||
eprintln!("Listening on {:?}...", listen);
|
eprintln!("Listening on {:?}...", listen);
|
||||||
|
|
|
@ -5,6 +5,10 @@ use std::path::PathBuf;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use tokio::join;
|
use tokio::join;
|
||||||
|
use tokio::task::spawn;
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use attic_server::config;
|
use attic_server::config;
|
||||||
|
|
||||||
|
@ -26,6 +30,12 @@ struct Opts {
|
||||||
/// Mode to run.
|
/// Mode to run.
|
||||||
#[clap(long, default_value = "monolithic")]
|
#[clap(long, default_value = "monolithic")]
|
||||||
mode: ServerMode,
|
mode: ServerMode,
|
||||||
|
|
||||||
|
/// Whether to enable tokio-console.
|
||||||
|
///
|
||||||
|
/// The console server will listen on its default port.
|
||||||
|
#[clap(long)]
|
||||||
|
tokio_console: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||||
|
@ -51,10 +61,11 @@ enum ServerMode {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
init_logging();
|
let opts = Opts::parse();
|
||||||
|
|
||||||
|
init_logging(opts.tokio_console);
|
||||||
dump_version();
|
dump_version();
|
||||||
|
|
||||||
let opts = Opts::parse();
|
|
||||||
let config = if let Some(config_path) = opts.config {
|
let config = if let Some(config_path) = opts.config {
|
||||||
config::load_config_from_path(&config_path)
|
config::load_config_from_path(&config_path)
|
||||||
} else if let Ok(config_env) = env::var("ATTIC_SERVER_CONFIG_BASE64") {
|
} else if let Ok(config_env) = env::var("ATTIC_SERVER_CONFIG_BASE64") {
|
||||||
|
@ -106,12 +117,30 @@ async fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logging() {
|
fn init_logging(tokio_console: bool) {
|
||||||
#[cfg(not(feature = "tokio-console"))]
|
let env_filter = EnvFilter::from_default_env();
|
||||||
tracing_subscriber::fmt::init();
|
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||||
|
.with_filter(env_filter);
|
||||||
|
|
||||||
#[cfg(feature = "tokio-console")]
|
let error_layer = ErrorLayer::default();
|
||||||
console_subscriber::init();
|
|
||||||
|
let console_layer = if tokio_console {
|
||||||
|
let (layer, server) = console_subscriber::ConsoleLayer::new();
|
||||||
|
spawn(server.serve());
|
||||||
|
Some(layer)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(fmt_layer)
|
||||||
|
.with(error_layer)
|
||||||
|
.with(console_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
if tokio_console {
|
||||||
|
eprintln!("Note: tokio-console is enabled");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump_version() {
|
fn dump_version() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use axum::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AuthState, RequestStateInner, State};
|
use super::{AuthState, RequestStateInner, State};
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerResult};
|
||||||
|
|
||||||
/// Initializes per-request state.
|
/// Initializes per-request state.
|
||||||
pub async fn init_request_state<B>(
|
pub async fn init_request_state<B>(
|
||||||
|
@ -50,7 +50,7 @@ pub async fn restrict_host<B>(
|
||||||
let allowed_hosts = &state.config.allowed_hosts;
|
let allowed_hosts = &state.config.allowed_hosts;
|
||||||
|
|
||||||
if !allowed_hosts.is_empty() && !allowed_hosts.iter().any(|h| h.as_str() == host) {
|
if !allowed_hosts.is_empty() && !allowed_hosts.iter().any(|h| h.as_str() == host) {
|
||||||
return Err(ServerError::RequestError(anyhow!("Bad Host")));
|
return Err(ErrorKind::RequestError(anyhow!("Bad Host")).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
|
|
|
@ -48,7 +48,7 @@ use serde::de;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
use crate::nix_manifest::{self, SpaceDelimitedList};
|
use crate::nix_manifest::{self, SpaceDelimitedList};
|
||||||
use attic::hash::Hash;
|
use attic::hash::Hash;
|
||||||
use attic::mime;
|
use attic::mime;
|
||||||
|
@ -252,9 +252,9 @@ impl FromStr for Compression {
|
||||||
"bzip2" => Ok(Self::Bzip2),
|
"bzip2" => Ok(Self::Bzip2),
|
||||||
"br" => Ok(Self::Brotli),
|
"br" => Ok(Self::Brotli),
|
||||||
"zstd" => Ok(Self::Zstd),
|
"zstd" => Ok(Self::Zstd),
|
||||||
_ => Err(ServerError::InvalidCompressionType {
|
_ => Err(ErrorKind::InvalidCompressionType {
|
||||||
name: s.to_string(),
|
name: s.to_string(),
|
||||||
}),
|
}.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use displaydoc::Display;
|
||||||
use serde::{de, ser, Deserialize, Serialize};
|
use serde::{de, ser, Deserialize, Serialize};
|
||||||
use serde_with::{formats::SpaceSeparator, StringWithSeparator};
|
use serde_with::{formats::SpaceSeparator, StringWithSeparator};
|
||||||
|
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerResult};
|
||||||
use deserializer::Deserializer;
|
use deserializer::Deserializer;
|
||||||
use serializer::Serializer;
|
use serializer::Serializer;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ where
|
||||||
T: for<'de> Deserialize<'de>,
|
T: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
let mut deserializer = Deserializer::from_str(s);
|
let mut deserializer = Deserializer::from_str(s);
|
||||||
T::deserialize(&mut deserializer).map_err(ServerError::ManifestSerializationError)
|
T::deserialize(&mut deserializer).map_err(|e| ErrorKind::ManifestSerializationError(e).into())
|
||||||
|
|
||||||
// FIXME: Reject extra output??
|
// FIXME: Reject extra output??
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ where
|
||||||
let mut serializer = Serializer::new();
|
let mut serializer = Serializer::new();
|
||||||
value
|
value
|
||||||
.serialize(&mut serializer)
|
.serialize(&mut serializer)
|
||||||
.map_err(ServerError::ManifestSerializationError)?;
|
.map_err(ErrorKind::ManifestSerializationError)?;
|
||||||
|
|
||||||
Ok(serializer.into_output())
|
Ok(serializer.into_output())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use tokio::fs::{self, File};
|
||||||
use tokio::io::{self, AsyncRead};
|
use tokio::io::{self, AsyncRead};
|
||||||
|
|
||||||
use super::{Download, RemoteFile, StorageBackend};
|
use super::{Download, RemoteFile, StorageBackend};
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LocalBackend {
|
pub struct LocalBackend {
|
||||||
|
@ -74,9 +74,9 @@ impl StorageBackend for LocalBackend {
|
||||||
let file = if let RemoteFile::Local(file) = file {
|
let file = if let RemoteFile::Local(file) = file {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return Err(ServerError::StorageError(anyhow::anyhow!(
|
return Err(ErrorKind::StorageError(anyhow::anyhow!(
|
||||||
"Does not understand the remote file reference"
|
"Does not understand the remote file reference"
|
||||||
)));
|
)).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::remove_file(self.get_path(&file.name))
|
fs::remove_file(self.get_path(&file.name))
|
||||||
|
@ -98,9 +98,9 @@ impl StorageBackend for LocalBackend {
|
||||||
let file = if let RemoteFile::Local(file) = file {
|
let file = if let RemoteFile::Local(file) = file {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return Err(ServerError::StorageError(anyhow::anyhow!(
|
return Err(ErrorKind::StorageError(anyhow::anyhow!(
|
||||||
"Does not understand the remote file reference"
|
"Does not understand the remote file reference"
|
||||||
)));
|
)).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let file = File::open(self.get_path(&file.name))
|
let file = File::open(self.get_path(&file.name))
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||||
|
|
||||||
use super::{Download, RemoteFile, StorageBackend};
|
use super::{Download, RemoteFile, StorageBackend};
|
||||||
use crate::error::{ServerError, ServerResult};
|
use crate::error::{ErrorKind, ServerError, ServerResult};
|
||||||
use attic::util::Finally;
|
use attic::util::Finally;
|
||||||
|
|
||||||
/// The chunk size for each part in a multipart upload.
|
/// The chunk size for each part in a multipart upload.
|
||||||
|
@ -113,9 +113,9 @@ impl S3Backend {
|
||||||
let file = if let RemoteFile::S3(file) = file {
|
let file = if let RemoteFile::S3(file) = file {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return Err(ServerError::StorageError(anyhow::anyhow!(
|
return Err(ErrorKind::StorageError(anyhow::anyhow!(
|
||||||
"Does not understand the remote file reference"
|
"Does not understand the remote file reference"
|
||||||
)));
|
)).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: Ugly
|
// FIXME: Ugly
|
||||||
|
|
Loading…
Add table
Reference in a new issue