/ backend / src / api / container_spec / manifests.rs
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  }