use axum::extract::{Request, State};
use axum::http::HeaderMap;
use axum::middleware::Next;
use axum::response::Response;
use axum::{Extension, Json};
use crate::audit::audit_log;
use crate::auth::{Actor, Permission};
use crate::error::Error;
use crate::model::{CompressRequest, DedupeRequest, RetainRequest};
use crate::namespace::Namespace;
use crate::state::Shared;
use crate::storage::{CompressReport, DedupeReport, SweepReport, VerifyReport};
pub(super) const KEEP_LIST_LIMIT: usize = 64 * 1024 * 1024;
pub(super) async fn writers_only(request: Request, next: Next) -> Result<Response, Error> {
request
.extensions()
.get::<Permission>()
.copied()
.ok_or(Error::Forbidden)?
.require_write()?;
Ok(next.run(request).await)
}
pub(super) async fn retain(
State(state): State<Shared>,
Extension(ns): Extension<Namespace>,
Extension(permission): Extension<Permission>,
headers: HeaderMap,
Json(request): Json<RetainRequest>,
) -> Result<Json<SweepReport>, Error> {
if !request.dry_run {
permission.require_admin()?;
}
let actor = attributed(&state, &headers, request.dry_run).await?;
let retained = request.oids.into_iter().collect();
let report = state
.store
.sweep(&ns, &retained, state.config.gc_grace, request.dry_run)
.await?;
if let Some(Actor(actor)) = actor {
audit_log!(
actor,
namespace = %ns,
swept = report.swept,
bytes = report.bytes,
within_grace = report.within_grace,
"a retain sweep unlinked what the keep list did not name"
);
}
Ok(Json(report))
}
pub(super) async fn dedupe(
State(state): State<Shared>,
Extension(ns): Extension<Namespace>,
Extension(permission): Extension<Permission>,
headers: HeaderMap,
Json(request): Json<DedupeRequest>,
) -> Result<Json<DedupeReport>, Error> {
permission.require_admin()?;
let actor = attributed(&state, &headers, request.dry_run).await?;
let report = state.store.dedupe(&ns, request.dry_run).await?;
if let Some(Actor(actor)) = actor {
audit_log!(
actor,
namespace = %ns,
adopted = report.adopted,
linked = report.linked,
reclaimed = report.reclaimed,
refused = report.refused,
"a repository's objects were folded into the shared store"
);
}
Ok(Json(report))
}
pub(super) async fn compress(
State(state): State<Shared>,
Extension(ns): Extension<Namespace>,
Extension(permission): Extension<Permission>,
headers: HeaderMap,
Json(request): Json<CompressRequest>,
) -> Result<Json<CompressReport>, Error> {
permission.require_admin()?;
let actor = attributed(&state, &headers, request.dry_run).await?;
let report = state.store.compress(&ns, request.dry_run).await?;
if let Some(Actor(actor)) = actor {
audit_log!(
actor,
namespace = %ns,
compressed = report.compressed,
before = report.before,
after = report.after,
"a repository's stored objects were rewritten compressed"
);
}
Ok(Json(report))
}
async fn attributed(
state: &Shared,
headers: &HeaderMap,
dry_run: bool,
) -> Result<Option<Actor>, Error> {
if dry_run {
return Ok(None);
}
Ok(Some(state.authorizer.actor(headers).await?))
}
pub(super) async fn audit(
State(state): State<Shared>,
Extension(ns): Extension<Namespace>,
Extension(permission): Extension<Permission>,
) -> Result<Json<VerifyReport>, Error> {
permission.require_admin()?;
let report = state.store.verify(&ns).await?;
Ok(Json(report))
}