Rust implementation of OCI Distribution Spec with granular access control
at main 133 lines 4.3 kB view raw
1use std::sync::Arc; 2 3use axum::{ 4 extract::DefaultBodyLimit, 5 routing::{delete, get, head, patch, post, put}, 6 Router, 7}; 8use clap::Parser; 9use tower_http::cors::CorsLayer; 10use utoipa::OpenApi; 11use utoipa_swagger_ui::SwaggerUi; 12 13mod admin; 14mod args; 15mod auth; 16mod blobs; 17mod errors; 18mod gc; 19mod health; 20mod manifests; 21mod meta; 22mod metrics; 23mod middleware; 24mod openapi; 25mod permissions; 26mod response; 27mod state; 28mod storage; 29mod tags; 30mod utils; 31mod validation; 32 33#[tokio::main] 34async fn main() { 35 let args = args::Args::parse(); 36 env_logger::init(); 37 log::info!("Starting grain build: {}", utils::get_build_info()); 38 39 // Shared app state 40 let shared_state = Arc::new(state::new_app(&args)); 41 let state_clone = shared_state.clone(); 42 43 let app = Router::new() 44 .route("/", get(meta::index)) // Index, info 45 // Health endpoints (no auth required) 46 .route("/health", get(health::health)) 47 .route("/health/live", get(health::liveness)) 48 .route("/health/ready", get(health::readiness)) 49 // Metrics endpoint (no auth for Prometheus scraping) 50 .route("/metrics", get(metrics::metrics)) 51 .route("/v2/", get(auth::get)) // end-1 52 .route( 53 "/v2/{org}/{repo}/manifests/{reference}", 54 head(manifests::head_manifest_by_reference), 55 ) 56 .route( 57 "/v2/{org}/{repo}/manifests/{reference}", 58 get(manifests::get_manifest_by_reference), 59 ) 60 .route( 61 "/v2/{org}/{repo}/blobs/{digest}", 62 get(blobs::get_blob_by_digest), 63 ) // end-2 64 .route( 65 "/v2/{org}/{repo}/blobs/{digest}", 66 head(blobs::head_blob_by_digest), 67 ) 68 .route( 69 "/v2/{org}/{repo}/blobs/uploads/", 70 post(blobs::post_blob_upload), 71 ) // end-4a, end-4b, end-11 72 .route( 73 "/v2/{org}/{repo}/blobs/uploads/{reference}", 74 patch(blobs::patch_blob_upload), 75 ) // end-5 76 .route( 77 "/v2/{org}/{repo}/blobs/uploads/{reference}", 78 put(blobs::put_blob_upload_by_reference), 79 ) // end-6 80 .route( 81 "/v2/{org}/{repo}/manifests/{reference}", 82 put(manifests::put_manifest_by_reference), 83 ) // end-7 84 .route("/v2/{org}/{repo}/tags/list", get(tags::get_tags_list)) // end-8a, end-8b 85 .route( 86 "/v2/{org}/{repo}/manifests/{reference}", 87 delete(manifests::delete_manifest_by_reference), 88 ) // end-9 89 .route( 90 "/v2/{org}/{repo}/blobs/{digest}", 91 delete(blobs::delete_blob_by_digest), 92 ) // end-10 93 // Admin API routes 94 .route("/admin/users", get(admin::list_users)) 95 .route("/admin/users", post(admin::create_user)) 96 .route("/admin/users/{username}", delete(admin::delete_user)) 97 .route( 98 "/admin/users/{username}/permissions", 99 post(admin::add_permission), 100 ) 101 .route( 102 "/admin/permissions", 103 post(admin::add_permission_with_username), 104 ) 105 .route("/admin/gc", post(admin::run_garbage_collection)) 106 // Catch-all routes for debugging 107 .route("/{*path}", head(meta::catch_all_head)) 108 .route("/{*path}", get(meta::catch_all_get)) 109 .route("/{*path}", post(meta::catch_all_post)) 110 .route("/{*path}", put(meta::catch_all_put)) 111 .route("/{*path}", patch(meta::catch_all_patch)) 112 .route("/{*path}", delete(meta::catch_all_delete)) 113 .with_state(state_clone) 114 .layer(DefaultBodyLimit::disable()) // Allow unlimited body size for blob uploads 115 .layer(axum::middleware::from_fn(middleware::track_metrics)) 116 .layer(CorsLayer::permissive()) 117 .merge( 118 SwaggerUi::new("/swagger-ui") 119 .url("/api-docs/openapi.json", openapi::AdminApiDoc::openapi()), 120 ); 121 122 log::info!("Listening on: {}", &args.host); 123 let listener = tokio::net::TcpListener::bind(&args.host).await.unwrap(); 124 125 // Mark server as ready after successful bind 126 { 127 let mut status = shared_state.server_status.lock().await; 128 *status = state::ServerStatus::Ready; 129 log::info!("Server status: Ready"); 130 } 131 132 axum::serve(listener, app).await.unwrap(); 133}