This commit is contained in:
2024-04-28 15:52:36 +02:00
parent 97e90d8178
commit 3fe3e5036d
13 changed files with 939 additions and 841 deletions

1467
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,11 @@ name = "packager"
path = "src/main.rs"
[features]
jaeger = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-jaeger", "tokio/tracing"]
opentelemetry = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:tracing-opentelemetry", "tokio/tracing"]
prometheus = ["dep:axum-prometheus"]
tokio-console = ["dep:console-subscriber"]
default = ["jaeger", "prometheus", "tokio-console"]
default = ["opentelemetry", "prometheus", "tokio-console"]
[profile.dev]
opt-level = 0
@@ -24,23 +24,22 @@ lto = "off"
version = "0.1"
[dependencies.opentelemetry]
version = "0.20"
version = "0.22"
optional = true
[dependencies.opentelemetry_sdk]
version = "0.22"
optional = true
[dependencies.tracing-opentelemetry]
version = "0.21"
version = "0.23"
optional = true
[dependencies.tracing-log]
version = "0.1"
[dependencies.opentelemetry-jaeger]
version = "0.19"
features = ["rt-tokio"]
optional = true
version = "0.2"
[dependencies.http]
version = "0.2"
version = "1.1"
[dependencies.log]
version = "0.4"
@@ -50,19 +49,19 @@ version = "4"
features = ["derive"]
[dependencies.axum]
version = "0.6"
features = ["headers", "macros"]
version = "0.7"
features = ["macros"]
[dependencies.tokio]
version = "1"
features = ["macros", "rt-multi-thread"]
[dependencies.console-subscriber]
version = "0.1"
version = "0.2"
optional = true
[dependencies.hyper]
version = "0.14"
version = "1.3"
features = ["full"]
[dependencies.tower]
@@ -70,7 +69,7 @@ version = "0.4"
features = ["timeout"]
[dependencies.tower-http]
version = "0.4"
version = "0.5"
features = ["trace", "request-id"]
[dependencies.tracing]
@@ -84,7 +83,7 @@ version = "0.3"
features = ["json", "env-filter"]
[dependencies.maud]
version = "0.25"
version = "0.26"
features = [
"axum",
]
@@ -123,14 +122,14 @@ features = ["derive"]
version = "0.1"
[dependencies.axum-prometheus]
version = "0.4"
version = "0.6"
optional = true
[dependencies.metrics]
version = "0.21"
version = "0.22"
[dependencies.sha2]
version = "0.10"
[dependencies.base64]
version = "0.21"
version = "0.22"

View File

@@ -1,9 +1,11 @@
use axum::{extract::State, middleware::Next, response::IntoResponse};
use axum::{
extract::{Request, State},
middleware::Next,
response::IntoResponse,
};
use futures::FutureExt;
use tracing::Instrument;
use hyper::Request;
use crate::models::user::User;
use super::models;
@@ -16,11 +18,21 @@ pub enum Config {
}
#[tracing::instrument(name = "check_auth", skip(state, request, next))]
pub async fn authorize<B>(
pub async fn authorize(
State(state): State<AppState>,
mut request: Request<B>,
next: Next<B>,
mut request: Request,
next: Next,
) -> Result<impl IntoResponse, Error> {
// We must not access `request` inside the async block above, otherwise there will be
// errors like the following:
//
// the trait `tower::Service<http::Request<axum::body::Body>>` is not implemented for
// `FromFn<fn(State<AppState>, Request<Body>, Next) -> impl Future<Output =
// Result<impl IntoResponse, Error>> {authorize}, AppState, Route, _>
//
// I am honestly not sure about the reason
let username_header = request.headers().get("x-auth-username");
let user = async {
let auth: Result<Result<User, AuthError>, Error> = match state.auth_config {
Config::Disabled { assume_user } => {
@@ -35,7 +47,7 @@ pub async fn authorize<B>(
};
Ok(user)
}
Config::Enabled => match request.headers().get("x-auth-username") {
Config::Enabled => match username_header {
None => Ok(Err(AuthError::AuthenticationHeaderMissing)),
Some(username) => match username.to_str() {
Err(e) => Ok(Err(AuthError::AuthenticationHeaderInvalid {
@@ -54,7 +66,6 @@ pub async fn authorize<B>(
},
},
};
auth
}
.instrument(tracing::debug_span!("authorize"))
@@ -72,15 +83,17 @@ pub async fn authorize<B>(
format!("packager_auth_{}_total", {
match auth {
Ok(_) => "success".to_string(),
Err(ref e) => format!("failure_{}", e.to_prom_metric_name()),
Err(ref e) => {
format!("failure_{}", e.to_prom_metric_name())
}
}
}),
1,
&match &auth {
Ok(user) => vec![("username", user.username.clone())],
Err(e) => e.to_prom_labels(),
}
);
)
.increment(1);
auth
})
})
@@ -89,5 +102,5 @@ pub async fn authorize<B>(
.await??;
request.extensions_mut().insert(user);
Ok(next.run(request).await)
Ok::<http::Response<axum::body::Body>, Error>(next.run(request).await)
}

View File

@@ -43,7 +43,7 @@ pub struct Args {
#[arg(long)]
pub database_url: String,
#[cfg(feature = "jaeger")]
#[cfg(feature = "opentelemetry")]
#[arg(long, value_enum, default_value_t = BoolArg::False)]
pub enable_opentelemetry: BoolArg,

View File

@@ -134,7 +134,7 @@ pub mod route {
use crate::{models::user::User, AppState};
use axum::{
body::{BoxBody, HttpBody},
body::Body,
extract::{Path, Query, State},
http::HeaderMap,
response::Response,
@@ -160,7 +160,7 @@ pub mod route {
headers: HeaderMap,
path: Path<Self::UrlParams>,
form: Form<Self::Form>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
}
#[async_trait]
@@ -176,7 +176,7 @@ pub mod route {
headers: HeaderMap,
query: Query<Self::QueryParams>,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
}
#[async_trait]
@@ -191,7 +191,7 @@ pub mod route {
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
async fn save(
user: Extension<User>,
@@ -199,14 +199,14 @@ pub mod route {
headers: HeaderMap,
path: Path<Self::UrlParams>,
form: Form<Self::UpdateForm>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
async fn cancel(
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
}
#[async_trait]
@@ -222,14 +222,14 @@ pub mod route {
headers: HeaderMap,
params: Self::UrlParams,
value: bool,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
async fn set_true(
Extension(user): Extension<User>,
State(state): State<AppState>,
headers: HeaderMap,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
<Self as ToggleFallback>::set(user, state, headers, path, true).await
}
@@ -238,15 +238,11 @@ pub mod route {
State(state): State<AppState>,
headers: HeaderMap,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
<Self as ToggleFallback>::set(user, state, headers, path, false).await
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send;
fn router() -> axum::Router<AppState>;
}
#[async_trait]
@@ -267,13 +263,13 @@ pub mod route {
ctx: &crate::Context,
state: AppState,
params: Self::UrlParams,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
async fn on(
Extension(user): Extension<User>,
State(state): State<AppState>,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let (ctx, state, params) = <Self as ToggleHtmx>::set(user, state, path, true).await?;
<Self as ToggleHtmx>::response(&ctx, state, params).await
}
@@ -282,25 +278,16 @@ pub mod route {
Extension(user): Extension<User>,
State(state): State<AppState>,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let (ctx, state, params) = <Self as ToggleHtmx>::set(user, state, path, false).await?;
<Self as ToggleHtmx>::response(&ctx, state, params).await
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send;
fn router() -> axum::Router<AppState>;
}
pub trait Toggle: ToggleHtmx + ToggleFallback {
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
fn router() -> axum::Router<AppState> {
axum::Router::new()
.merge(<Self as ToggleHtmx>::router())
.merge(<Self as ToggleFallback>::router())
@@ -318,14 +305,10 @@ pub mod route {
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
) -> Result<Response<Body>, crate::Error>;
}
pub trait Router: Create + Delete {
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send;
fn router() -> axum::Router<AppState>;
}
}

View File

@@ -2,7 +2,7 @@ pub mod list;
pub use list::List;
use axum::{
body::{BoxBody, HttpBody},
body::Body,
extract::{Form, Path, State as StateExtractor},
http::HeaderMap,
response::{IntoResponse, Redirect, Response},
@@ -653,7 +653,7 @@ impl route::Create for Todo {
headers: HeaderMap,
Path((trip_id,)): Path<Self::UrlParams>,
Form(form): Form<Self::Form>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let ctx = Context::build(current_user);
// method output is not required as we reload the whole trip todos anyway
let _todo_item = <Self as crud::Create>::create(
@@ -700,7 +700,7 @@ impl route::Delete for Todo {
StateExtractor(state): StateExtractor<AppState>,
_headers: HeaderMap,
Path((trip_id, todo_id)): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let ctx = Context::build(current_user);
let deleted = <Self as crud::Delete>::delete(
&ctx,
@@ -737,12 +737,7 @@ impl route::Delete for Todo {
}
impl route::Router for Todo {
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
fn router() -> axum::Router<AppState> {
axum::Router::new()
.route("/new", axum::routing::post(<Self as route::Create>::create))
.route(
@@ -972,7 +967,7 @@ impl route::ToggleFallback for StateUpdate {
headers: HeaderMap,
(trip_id, todo_id): (Uuid, Uuid),
value: bool,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let ctx = Context::build(current_user);
<Self as crud::Toggle>::set(
&ctx,
@@ -988,12 +983,7 @@ impl route::ToggleFallback for StateUpdate {
Ok(Redirect::to(get_referer(&headers)?).into_response())
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
fn router() -> axum::Router<AppState> {
axum::Router::new()
.route(Self::URL_TRUE, post(Self::set_true))
.route(Self::URL_FALSE, post(Self::set_false))
@@ -1023,7 +1013,7 @@ impl route::ToggleHtmx for StateUpdate {
ctx: &Context,
state: AppState,
(trip_id, todo_id): Self::UrlParams,
) -> Result<Response<BoxBody>, crate::Error> {
) -> Result<Response<Body>, crate::Error> {
let todo_item = Todo::find(
ctx,
&state.database_pool,
@@ -1047,12 +1037,7 @@ impl route::ToggleHtmx for StateUpdate {
.into_response())
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
fn router() -> axum::Router<AppState> {
axum::Router::new()
.route(Self::URL_TRUE, post(Self::on))
.route(Self::URL_FALSE, post(Self::off))

View File

@@ -6,6 +6,7 @@ use std::str::FromStr;
use packager::{
auth, cli, models, routing, sqlite, telemetry, AppState, ClientState, Error, StartError,
};
use tokio::net::TcpListener;
struct MainResult(Result<(), Error>);
@@ -41,7 +42,7 @@ async fn main() -> MainResult {
};
telemetry::tracing::init(
#[cfg(feature = "jaeger")]
#[cfg(feature = "opentelemetry")]
if args.enable_opentelemetry.into() {
telemetry::tracing::OpenTelemetryConfig::Enabled
} else {
@@ -117,19 +118,19 @@ async fn main() -> MainResult {
tracing::debug!("listening on {}", addr);
if let Err(e) = axum::Server::try_bind(&addr)
.map_err(|e| {
axum::serve(
TcpListener::bind(&addr).await.map_err(|e| {
Error::Start(StartError::BindError {
addr,
message: e.to_string(),
})
})?
.serve(app.into_make_service())
})?,
app,
)
.await
{
return Err(<hyper::Error as Into<Error>>::into(e));
}
Ok(())
// Error = Infallible
.unwrap();
unreachable!()
});
// now we wait for all tasks. none of them are supposed to finish

View File

@@ -325,7 +325,7 @@ impl InventoryItem {
weight: u32,
) -> Result<Uuid, Error> {
let user_id = ctx.user.id.to_string();
let weight = i64::try_from(weight).unwrap();
let weight = i64::from(weight);
let id_param = id.to_string();
crate::execute_returning_uuid!(

View File

@@ -1036,28 +1036,6 @@ impl Trip {
ctx: &Context,
pool: &sqlite::Pool,
) -> Result<(), Error> {
struct Row {
id: String,
name: String,
active: i32,
}
impl TryFrom<Row> for TripType {
type Error = Error;
fn try_from(row: Row) -> Result<Self, Self::Error> {
Ok(TripType {
id: Uuid::try_parse(&row.id)?,
name: row.name,
active: match row.active {
0 => false,
1 => true,
_ => unreachable!(),
},
})
}
}
let user_id = ctx.user.id.to_string();
let id = self.id.to_string();
let types = crate::query_all!(
@@ -1066,7 +1044,7 @@ impl Trip {
component: sqlite::Component::Trips,
},
pool,
Row,
TripTypeRow,
TripType,
"
SELECT
@@ -1406,6 +1384,28 @@ pub struct TripType {
pub active: bool,
}
struct TripTypeRow {
id: String,
name: String,
active: i32,
}
impl TryFrom<TripTypeRow> for TripType {
type Error = Error;
fn try_from(row: TripTypeRow) -> Result<Self, Self::Error> {
Ok(TripType {
id: Uuid::try_parse(&row.id)?,
name: row.name,
active: match row.active {
0 => false,
1 => true,
_ => unreachable!(),
},
})
}
}
impl TripsType {
#[tracing::instrument]
pub async fn all(ctx: &Context, pool: &sqlite::Pool) -> Result<Vec<Self>, Error> {

View File

@@ -1,7 +1,6 @@
use axum::{
error_handling::HandleErrorLayer,
http::header::HeaderMap,
http::StatusCode,
http::{header::HeaderMap, StatusCode},
middleware,
routing::{get, post},
BoxError, Router,

View File

@@ -137,7 +137,7 @@ pub fn sqlx_query(
("query_type", classification.query_type.to_string()),
("query_component", classification.component.to_string()),
]);
metrics::counter!("packager_database_queries_total", 1, &labels);
metrics::counter!("packager_database_queries_total", &labels).increment(1);
}
// This does not work, as the query*! macros expect a string literal for the query, so

View File

@@ -4,11 +4,10 @@ use axum::routing::get;
use axum::Router;
use axum_prometheus::{Handle, MakeDefaultHandle, PrometheusMetricLayerBuilder};
use tokio::net::TcpListener;
use crate::{Error, StartError};
pub struct LabelBool(bool);
/// Serves metrics on the specified `addr`.
///
/// You will get two outputs back: Another router, and a task that you have
@@ -25,19 +24,19 @@ pub fn prometheus_server(
let app = Router::new().route("/metrics", get(|| async move { metric_handle.render() }));
let task = async move {
if let Err(e) = axum::Server::try_bind(&addr)
.map_err(|e| {
axum::serve(
TcpListener::bind(addr).await.map_err(|e| {
Error::Start(StartError::BindError {
message: e.to_string(),
addr,
message: e.to_string(),
})
})?
.serve(app.into_make_service())
})?,
app,
)
.await
{
return Err(<hyper::Error as Into<Error>>::into(e));
}
Ok(())
// Error = Infallible
.unwrap();
unreachable!()
};
(router.layer(prometheus_layer), task)

View File

@@ -22,8 +22,10 @@ use tracing::Instrument;
use uuid::Uuid;
#[cfg(feature = "jaeger")]
use opentelemetry::{global, runtime::Tokio};
#[cfg(feature = "opentelemetry")]
use opentelemetry::{global, trace::TracerProvider as _};
#[cfg(feature = "opentelemetry")]
use opentelemetry_sdk::trace::TracerProvider;
pub enum OpenTelemetryConfig {
Enabled,
@@ -64,17 +66,8 @@ fn get_stdout_layer<
stdout_layer.boxed()
}
trait Forwarder {
type Config;
fn build(
config: Self::Config,
shutdown_functions: &mut Vec<ShutdownFunction>,
) -> Option<Box<dyn tracing_subscriber::Layer<dyn tracing::Subscriber>>>;
}
#[cfg(feature = "jaeger")]
fn get_jaeger_layer<
#[cfg(feature = "opentelemetry")]
fn get_opentelemetry_layer<
T: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
>(
config: &OpenTelemetryConfig,
@@ -82,16 +75,20 @@ fn get_jaeger_layer<
) -> Option<impl tracing_subscriber::Layer<T>> {
match config {
OpenTelemetryConfig::Enabled => {
global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
// Sets up the machinery needed to export data to Jaeger
global::set_text_map_propagator(
opentelemetry_sdk::propagation::TraceContextPropagator::new(),
);
// Sets up the machinery needed to export data to an opentelemetry endpoint.
// There are other OTel crates that provide pipelines for the vendors
// mentioned earlier.
let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_service_name(env!("CARGO_PKG_NAME"))
.with_max_packet_size(50_000)
.with_auto_split_batch(true)
.install_batch(Tokio)
.unwrap();
let provider = TracerProvider::builder()
// .with_service_name()
// .with_max_packet_size(50_000)
// .with_auto_split_batch(true)
// .install_batch(Tokio)
.build();
let tracer = provider.tracer(env!("CARGO_PKG_NAME"));
let opentelemetry_filter = {
Targets::new()
@@ -122,7 +119,7 @@ fn get_jaeger_layer<
type ShutdownFunction = Box<dyn FnOnce() -> Result<(), Box<dyn std::error::Error>>>;
pub async fn init<Func, T>(
#[cfg(feature = "jaeger")] opentelemetry_config: OpenTelemetryConfig,
#[cfg(feature = "opentelemetry")] opentelemetry_config: OpenTelemetryConfig,
#[cfg(feature = "tokio-console")] tokio_console_config: TokioConsoleConfig,
args: crate::cli::Args,
f: Func,
@@ -131,12 +128,12 @@ where
Func: FnOnce(crate::cli::Args) -> Pin<Box<dyn Future<Output = T>>>,
T: std::process::Termination,
{
// mut is dependent on features (it's only required when jaeger is set), so
// mut is dependent on features (it's only required when opentelemetry is set), so
// let's just disable the lint
#[cfg(feature = "jaeger")]
#[cfg(feature = "opentelemetry")]
let mut shutdown_functions: Vec<ShutdownFunction> = vec![];
#[cfg(not(feature = "jaeger"))]
#[cfg(not(feature = "opentelemetry"))]
let shutdown_functions: Vec<ShutdownFunction> = vec![];
#[cfg(feature = "tokio-console")]
@@ -147,16 +144,17 @@ where
let stdout_layer = get_stdout_layer();
#[cfg(feature = "jaeger")]
let jaeger_layer = get_jaeger_layer(&opentelemetry_config, &mut shutdown_functions);
#[cfg(feature = "opentelemetry")]
let opentelemetry_layer =
get_opentelemetry_layer(&opentelemetry_config, &mut shutdown_functions);
let registry = Registry::default();
#[cfg(feature = "tokio-console")]
let registry = registry.with(console_layer);
#[cfg(feature = "jaeger")]
let registry = registry.with(jaeger_layer);
#[cfg(feature = "opentelemetry")]
let registry = registry.with(opentelemetry_layer);
// just an example, you can actuall pass Options here for layers that might be
// set/unset at runtime