From ed6ef2cdfdf089d88ca9837d35db126511571a1e Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Sat, 15 Nov 2025 09:55:38 +0100 Subject: [PATCH] add blob and manifest delete --- src/blobs.rs | 79 ++++++++++++++++++++++++++++++++++++++++-------- src/manifests.rs | 71 +++++++++++++++++++++++++++++++++++++------ src/storage.rs | 44 +++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 22 deletions(-) diff --git a/src/blobs.rs b/src/blobs.rs index e4173c0..0b8ddcd 100644 --- a/src/blobs.rs +++ b/src/blobs.rs @@ -10,7 +10,6 @@ // | end-11 | `POST` | `/v2//blobs/uploads/?mount=&from=` | `201` | `404` | use serde::Deserialize; -use serde_json::{json, Value}; use std::sync::Arc; use crate::{ @@ -21,7 +20,7 @@ use axum::{ body::Body, extract::{Path, Query, State}, http::{HeaderMap, StatusCode}, - response::{Json, Response}, + response::Response, }; use bytes::Bytes; @@ -348,17 +347,71 @@ pub(crate) async fn put_blob_upload_by_reference( // end-10 DELETE /v2/:name/blobs/:digest pub(crate) async fn delete_blob_by_digest( - State(data): State>, - Path(name): Path, - Path(digest): Path, -) -> Json { - let status = data.server_status.lock().await; + State(state): State>, + Path((org, repo, digest_string)): Path<(String, String, String)>, + headers: HeaderMap, +) -> Response { + let host = &state.args.host; + + // Authenticate + if auth::get(State(state.clone()), headers).await.status() != StatusCode::OK { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .header( + "WWW-Authenticate", + format!("Basic realm=\"{}\", charset=\"UTF-8\"", host), + ) + .body(Body::from("401 Unauthorized")) + .unwrap(); + } + + // Clean digest (strip sha256: prefix if present) + let clean_digest = digest_string + .strip_prefix("sha256:") + .unwrap_or(&digest_string); + log::info!( - "blobs/delete_blob_by_digest: name: {}, digest: {}", - name, - digest + "blobs/delete_blob_by_digest: org: {}, repo: {}, digest: {}", + org, + repo, + clean_digest ); - Json(json!({ - "not_implemented": format!("name {} digest {} server_status {}", name, digest, status) - })) + + // Delete blob + match storage::delete_blob(&org, &repo, clean_digest) { + Ok(()) => { + log::info!("Deleted blob {}/{}/{}", org, repo, clean_digest); + + Response::builder() + .status(StatusCode::ACCEPTED) + .body(Body::empty()) + .unwrap() + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + log::warn!( + "Attempted to delete non-existent blob {}/{}/{}", + org, + repo, + clean_digest + ); + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("404 Not Found")) + .unwrap() + } else { + log::error!( + "Failed to delete blob {}/{}/{}: {}", + org, + repo, + clean_digest, + e + ); + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from("Internal server error")) + .unwrap() + } + } + } } diff --git a/src/manifests.rs b/src/manifests.rs index a0e0dd6..0aeb36d 100644 --- a/src/manifests.rs +++ b/src/manifests.rs @@ -188,16 +188,69 @@ pub(crate) async fn put_manifest_by_reference( // end-9 DELETE /v2/:name/manifests/:reference pub(crate) async fn delete_manifest_by_reference( - Path(name): Path, - Path(reference): Path, + State(state): State>, + Path((org, repo, reference)): Path<(String, String, String)>, + headers: HeaderMap, ) -> Response { + let host = &state.args.host; + + // Authenticate + if auth::get(State(state.clone()), headers).await.status() != StatusCode::OK { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .header( + "WWW-Authenticate", + format!("Basic realm=\"{}\", charset=\"UTF-8\"", host), + ) + .body(Body::from("401 Unauthorized")) + .unwrap(); + } + + // Clean reference (strip sha256: prefix if present) + let clean_reference = reference.strip_prefix("sha256:").unwrap_or(&reference); + log::info!( - "manifests/delete_manifest_by_reference: name: {}, reference: {}", - name, - reference + "manifests/delete_manifest_by_reference: org: {}, repo: {}, reference: {}", + org, + repo, + clean_reference ); - Response::builder() - .status(StatusCode::NOT_IMPLEMENTED) - .body(Body::from("501 Not Implemented")) - .unwrap() + + // Delete manifest + match storage::delete_manifest(&org, &repo, clean_reference) { + Ok(()) => { + log::info!("Deleted manifest {}/{}/{}", org, repo, clean_reference); + + Response::builder() + .status(StatusCode::ACCEPTED) + .body(Body::empty()) + .unwrap() + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + log::warn!( + "Attempted to delete non-existent manifest {}/{}/{}", + org, + repo, + clean_reference + ); + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("404 Not Found")) + .unwrap() + } else { + log::error!( + "Failed to delete manifest {}/{}/{}: {}", + org, + repo, + clean_reference, + e + ); + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from("Internal server error")) + .unwrap() + } + } + } } diff --git a/src/storage.rs b/src/storage.rs index 243786e..3e07237 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -275,3 +275,47 @@ pub(crate) fn delete_upload_session( ); std::fs::remove_file(upload_path) } + +pub(crate) fn delete_manifest( + org: &str, + repo: &str, + reference: &str, +) -> Result<(), std::io::Error> { + let sanitized_org = sanitize_string(org); + let sanitized_repo = sanitize_string(repo); + let sanitized_reference = sanitize_string(reference); + + let manifest_path = format!( + "./tmp/manifests/{}/{}/{}", + sanitized_org, sanitized_repo, sanitized_reference + ); + + if !std::path::Path::new(&manifest_path).exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Manifest not found", + )); + } + + std::fs::remove_file(manifest_path) +} + +pub(crate) fn delete_blob(org: &str, repo: &str, digest: &str) -> Result<(), std::io::Error> { + let sanitized_org = sanitize_string(org); + let sanitized_repo = sanitize_string(repo); + let sanitized_digest = sanitize_string(digest); + + let blob_path = format!( + "./tmp/blobs/{}/{}/{}", + sanitized_org, sanitized_repo, sanitized_digest + ); + + if !std::path::Path::new(&blob_path).exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Blob not found", + )); + } + + std::fs::remove_file(blob_path) +} -- 2.51.2