/ src / api / container_spec / manifests.rs
manifests.rs
  1  use rocket::{
  2      fs::NamedFile,
  3      http::{ContentType, Header, Status},
  4      request::{self, FromRequest},
  5      Request, State,
  6  };
  7  use sqlx::Pool;
  8  
  9  use crate::{
 10      config::Config,
 11      db::DB,
 12      services::{self, get_manifest_service},
 13  };
 14  
 15  use super::{
 16      CONTENT_LENGTH_HEADER_NAME, CONTENT_TYPE_HEADER_NAME, DOCKER_CONTENT_DIGEST_HEADER_NAME,
 17      LOCATION_HEADER_NAME,
 18  };
 19  
 20  #[derive(Responder, Debug)]
 21  pub struct GetManifestResponseData<'a> {
 22      file: NamedFile,
 23      content_type: ContentType,
 24      docker_digest: Header<'a>,
 25  }
 26  
 27  #[derive(Responder, Debug)]
 28  pub enum GetManifestResponse<'a> {
 29      #[response(status = 200)]
 30      Success(GetManifestResponseData<'a>),
 31      #[response(status = 404)]
 32      FileNotFound(&'a str),
 33      #[response(status = 500)]
 34      Failure(&'a str),
 35  }
 36  
 37  #[get("/v2/<name>/manifests/<reference>")]
 38  pub async fn get_manifest<'a>(
 39      name: &str,
 40      reference: &str,
 41      db_pool: &State<Pool<DB>>,
 42      config: &State<Config>,
 43  ) -> GetManifestResponse<'a> {
 44      match get_manifest_service::find_manifest(
 45          db_pool,
 46          name.to_string(),
 47          reference.to_string(),
 48          config,
 49      )
 50      .await
 51      {
 52          Ok(Some((manifest, blob, file))) => {
 53              println!("Manifest found for {name}/{reference}");
 54              GetManifestResponse::Success(GetManifestResponseData {
 55                  file: file,
 56                  content_type: ContentType::new(
 57                      manifest.content_type_top,
 58                      manifest.content_type_sub,
 59                  ),
 60                  docker_digest: Header::new(DOCKER_CONTENT_DIGEST_HEADER_NAME, blob.digest),
 61              })
 62          }
 63          Ok(None) => {
 64              println!("Failed to find manifest {name}/{reference}");
 65              GetManifestResponse::FileNotFound("File not found")
 66          }
 67          Err(e) => {
 68              error!("Failed to get manifest, err: {e:?}");
 69              GetManifestResponse::Failure("An error occurred")
 70          }
 71      }
 72  }
 73  
 74  #[derive(Responder, Debug)]
 75  pub struct PutManifestResponseData<'a> {
 76      response: &'a str,
 77      location: Header<'a>,
 78      docker_content_digest: Header<'a>,
 79  }
 80  
 81  #[derive(Responder, Debug)]
 82  pub enum PutManifestResponse<'a> {
 83      #[response(status = 201)]
 84      Success(PutManifestResponseData<'a>),
 85      #[response(status = 400)]
 86      BadRequest(String),
 87      #[response(status = 500)]
 88      Failure(&'a str),
 89  }
 90  
 91  pub struct ManifestHeaders {
 92      content_length: usize,
 93      content_type: String,
 94  }
 95  
 96  #[rocket::async_trait]
 97  impl<'r> FromRequest<'r> for ManifestHeaders {
 98      type Error = PutManifestResponse<'r>;
 99  
100      async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
101          let content_length: usize = match req.headers().get_one(CONTENT_LENGTH_HEADER_NAME) {
102              Some(h) => match h.parse() {
103                  Ok(val) => val,
104                  Err(e) => {
105                      error!("Failed to parse content_length header, err: {e:?}");
106                      return request::Outcome::Failure((
107                          Status::BadRequest,
108                          PutManifestResponse::BadRequest(format!(
109                              "Invalid header {CONTENT_LENGTH_HEADER_NAME}"
110                          )),
111                      ));
112                  }
113              },
114              None => {
115                  error!("Missing content_length header");
116                  return request::Outcome::Failure((
117                      Status::BadRequest,
118                      PutManifestResponse::BadRequest(format!(
119                          "Missing header {CONTENT_LENGTH_HEADER_NAME}"
120                      )),
121                  ));
122              }
123          };
124  
125          let content_type = match req.headers().get_one(CONTENT_TYPE_HEADER_NAME) {
126              Some(h) => h.to_string(),
127              None => {
128                  error!("Missing content_type header");
129                  return request::Outcome::Failure((
130                      Status::BadRequest,
131                      PutManifestResponse::BadRequest(format!(
132                          "Missing header {CONTENT_TYPE_HEADER_NAME}"
133                      )),
134                  ));
135              }
136          };
137  
138          request::Outcome::Success(Self {
139              content_length,
140              content_type,
141          })
142      }
143  }
144  
145  #[put("/v2/<name>/manifests/<reference>", data = "<manifest_data>")]
146  pub async fn put_manifest<'a>(
147      name: &str,
148      reference: &str,
149      headers: ManifestHeaders,
150      manifest_data: Vec<u8>,
151      config: &State<Config>,
152      db_pool: &State<Pool<DB>>,
153  ) -> PutManifestResponse<'a> {
154      match services::upload_manifest_service::upload_manifest(
155          name.to_string(),
156          reference.to_string(),
157          headers.content_length,
158          headers.content_type,
159          manifest_data,
160          config,
161          db_pool,
162      )
163      .await
164      {
165          Ok((manifest_id, digest)) => PutManifestResponse::Success(PutManifestResponseData {
166              response: "Upload manifest successful",
167              location: Header::new(LOCATION_HEADER_NAME, format!("/{manifest_id}")),
168              docker_content_digest: Header::new(DOCKER_CONTENT_DIGEST_HEADER_NAME, digest),
169          }),
170          Err(e) => {
171              error!("Failed to upload manifest {e:?}");
172              PutManifestResponse::Failure("Failed to upload manifest")
173          }
174      }
175  }