manifests.rs
1 use rocket::{ 2 fs::NamedFile, 3 http::{ContentType, Header}, 4 State, 5 }; 6 use sqlx::Pool; 7 use uuid::Uuid; 8 9 use crate::{ 10 config::Config, 11 db::DB, 12 header, 13 registry_error::{RegistryError, RegistryResult}, 14 services::{self, delete_manifest_service, get_manifest_service}, 15 }; 16 17 use super::{ 18 blobs::utils::content_length::ContentLength, Auth, DOCKER_CONTENT_DIGEST_HEADER_NAME, 19 LOCATION_HEADER_NAME, OCI_SUBJECT_HEADER_NAME, 20 }; 21 22 #[derive(Responder, Debug)] 23 pub struct GetManifestResponseData<'a> { 24 file: NamedFile, 25 content_type: ContentType, 26 docker_digest: Header<'a>, 27 } 28 29 #[derive(Responder, Debug)] 30 pub enum GetManifestResponse<'a> { 31 #[response(status = 200)] 32 Success(GetManifestResponseData<'a>), 33 #[response(status = 404)] 34 FileNotFound(&'a str), 35 #[response(status = 500)] 36 Failure(&'a str), 37 } 38 39 #[get("/v2/<name>/manifests/<reference>")] 40 pub async fn get_manifest<'a>( 41 name: &str, 42 reference: &str, 43 db_pool: &State<Pool<DB>>, 44 config: &State<Config>, 45 ) -> GetManifestResponse<'a> { 46 match get_manifest_service::find_manifest(db_pool, name, reference, config).await { 47 Ok(Some(manifest_info)) => { 48 info!("Manifest found for {name}/{reference}"); 49 GetManifestResponse::Success(GetManifestResponseData { 50 file: manifest_info.named_file, 51 content_type: ContentType::new( 52 manifest_info.manifest.content_type_top, 53 manifest_info.manifest.content_type_sub, 54 ), 55 docker_digest: Header::new( 56 DOCKER_CONTENT_DIGEST_HEADER_NAME, 57 manifest_info.manifest.digest, 58 ), 59 }) 60 } 61 Ok(None) => { 62 warn!("Failed to find manifest {name}/{reference}"); 63 GetManifestResponse::FileNotFound("File not found") 64 } 65 Err(e) => { 66 error!("Failed to get manifest, err: {e:?}"); 67 GetManifestResponse::Failure("An error occurred") 68 } 69 } 70 } 71 72 #[derive(Responder, Debug)] 73 pub struct PutManifestResponseData<'a> { 74 response: &'a str, 75 location: Header<'a>, 76 docker_content_digest: Header<'a>, 77 oci_subject: Header<'a>, 78 } 79 80 #[derive(Responder, Debug)] 81 pub enum PutManifestResponse<'a> { 82 #[response(status = 201)] 83 Success(PutManifestResponseData<'a>), 84 #[response(status = 400)] 85 BadRequest(String), 86 #[response(status = 500)] 87 Failure(&'a str), 88 } 89 90 #[put("/v2/<name>/manifests/<reference>", data = "<data>")] 91 pub async fn put_manifest<'a>( 92 db_pool: &State<Pool<DB>>, 93 config: &State<Config>, 94 _auth: Auth, 95 name: &str, 96 reference: &str, 97 content_length: ContentLength, 98 content_type: &ContentType, 99 data: Vec<u8>, 100 ) -> PutManifestResponse<'a> { 101 match upload_manifest( 102 db_pool, 103 config, 104 name, 105 reference, 106 content_type, 107 content_length, 108 data, 109 ) 110 .await 111 { 112 Ok((manifest_id, digest, subject_digest)) => { 113 PutManifestResponse::Success(PutManifestResponseData { 114 response: "Upload manifest successful", 115 location: header!(LOCATION_HEADER_NAME, format!("/{manifest_id}")), 116 docker_content_digest: header!(DOCKER_CONTENT_DIGEST_HEADER_NAME, digest), 117 oci_subject: header!( 118 OCI_SUBJECT_HEADER_NAME, 119 subject_digest.unwrap_or(String::new()) 120 ), 121 }) 122 } 123 Err(e) => { 124 error!("Failed to upload manifest {e:?}"); 125 PutManifestResponse::Failure("Failed to upload manifest") 126 } 127 } 128 } 129 130 async fn upload_manifest( 131 db_pool: &Pool<DB>, 132 config: &Config, 133 name: &str, 134 reference: &str, 135 manifest_type: &ContentType, 136 content_length: ContentLength, 137 data: Vec<u8>, 138 ) -> RegistryResult<(Uuid, String, Option<String>)> { 139 content_length.validate_data_length(data.len())?; 140 141 let (id, digest, subject_digest) = services::upload_manifest_service::upload_manifest( 142 db_pool, 143 config, 144 name, 145 reference, 146 manifest_type, 147 data, 148 ) 149 .await?; 150 151 Ok((id, digest, subject_digest)) 152 } 153 154 #[derive(Responder)] 155 pub enum DeleteManifestResponse { 156 #[response(status = 202)] 157 Success(()), 158 #[response(status = 404)] 159 NotFound(()), 160 #[response(status = 500)] 161 Failure(()), 162 } 163 164 #[delete("/v2/<name>/manifests/<reference>")] 165 pub async fn delete_manifest( 166 db_pool: &State<Pool<DB>>, 167 config: &State<Config>, 168 _auth: Auth, 169 name: &str, 170 reference: &str, 171 ) -> DeleteManifestResponse { 172 if reference.starts_with("sha256:") { 173 info!("Reference understood to be digest {reference}"); 174 if let Err(err) = 175 delete_manifest_service::delete_manifest(db_pool, config, name, reference).await 176 { 177 match err { 178 RegistryError::ManifestNotFound => { 179 return DeleteManifestResponse::NotFound(()); 180 } 181 err => { 182 error!("Failed to delete manifest, err: {err:?}"); 183 return DeleteManifestResponse::Failure(()); 184 } 185 } 186 } 187 } else { 188 info!("Reference understood to be tag {reference}"); 189 if let Err(err) = delete_manifest_service::delete_tag(db_pool, name, reference).await { 190 error!("Failed to delete tag, err: {err:?}"); 191 return DeleteManifestResponse::Failure(()); 192 } 193 } 194 195 DeleteManifestResponse::Success(()) 196 }