Proxying and deserializing the upstream manifest API
This commit is contained in:
parent
b3c953f427
commit
70b5a5371c
65
src/main.rs
65
src/main.rs
|
@ -4,9 +4,7 @@
|
|||
|
||||
extern crate config;
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate surf;
|
||||
extern crate tide;
|
||||
|
@ -83,6 +81,25 @@ impl AppState {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated GET request to the given registry
|
||||
*/
|
||||
async fn make_get_request(url: &str, authorization: &str, accepts: &str) -> Result<surf::Response, surf::Error> {
|
||||
debug!("Making GET request to {}", url);
|
||||
|
||||
surf::get(url)
|
||||
.set_header("Authorization", authorization)
|
||||
.set_header("Accepts", accepts).await
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple convenience function for grabbing the HeaderValue out of the tide::Request and accessing
|
||||
* it as a string so contaminate can pass that to surf
|
||||
*/
|
||||
fn header_as_str(req: &Request<AppState>, header: &str, default: &str) -> String {
|
||||
req.header(header).map_or(default, |hv| hv.as_str()).to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy the given response to the upstream registry and return the response
|
||||
* back to the client request it.
|
||||
|
@ -94,26 +111,20 @@ async fn proxy_upstream(req: Request<AppState>) -> Result<Response, tide::Error>
|
|||
* We need to send the Authorization header along as well, otherwise
|
||||
* the upstream repository might complain that we're not authorized
|
||||
*/
|
||||
let token = req.header("Authorization").map_or("", |h| h.as_str());
|
||||
let accepts = req.header("Accept").map_or("", |h| h.as_str());
|
||||
let token = header_as_str(&req, "Authorization", "");
|
||||
let accepts = header_as_str(&req, "Accept", "");
|
||||
|
||||
let outbound = surf::get(full_url)
|
||||
.set_header("Authorization", token)
|
||||
.set_header("Accept", accepts);
|
||||
|
||||
debug!("Preparing to send request");
|
||||
if let Ok(mut u_res) = outbound.await {
|
||||
if let Ok(mut u_res) = make_get_request(&full_url, &token, &accepts).await {
|
||||
let body = u_res.body_string().await;
|
||||
debug!("Body received");
|
||||
debug!("Body received from {}", full_url);
|
||||
match body {
|
||||
Ok(body) => {
|
||||
trace!("upstream response for {}:\n{}", req.url(), body);
|
||||
/*
|
||||
* If we don't explicitly set the content type here, the client will think
|
||||
* that we're sending back a v1 manifest schema and complain about a "missing
|
||||
* signature key"
|
||||
*/
|
||||
//debug!("upstream headers: {:?}", u_res.headers());
|
||||
debug!("upstream response for {}:\n{}", req.url(), body);
|
||||
let content_type = u_res.header("Content-Type").map_or("text/plain", |h| h.as_str());
|
||||
let mut res = Response::new(u_res.status());
|
||||
res.set_body(body);
|
||||
|
@ -139,6 +150,17 @@ async fn proxy_upstream(req: Request<AppState>) -> Result<Response, tide::Error>
|
|||
*
|
||||
* This will return a Response to the client which conforms to the manifest
|
||||
* specification.
|
||||
*
|
||||
* The manifests API is defined in the "Pulling an Image" part of the HTTP
|
||||
* registry API documentation, see here:
|
||||
* <https://docs.docker.com/registry/spec/api/#pulling-an-image>
|
||||
*
|
||||
* This route will:
|
||||
* - fetch the upstream manifest in order to fetch the image configuration
|
||||
* - fetch the image configuration, which must have its diff_ids overwritten
|
||||
* - re-compute the digest of the image configuration
|
||||
* - grab all the layers which must be inserted into the manifest
|
||||
* - generate the updated manifest
|
||||
*/
|
||||
async fn fetch_digest(req: Request<AppState>) -> Result<Response, tide::Error> {
|
||||
let org: String = req.param("org").unwrap_or("".to_string());
|
||||
|
@ -147,7 +169,19 @@ async fn fetch_digest(req: Request<AppState>) -> Result<Response, tide::Error> {
|
|||
|
||||
if req.state().override_exists(&org, &image, &digest) {
|
||||
error!("Override exists, we should serve this request");
|
||||
Ok(Response::new(200))
|
||||
let full_url = format!("{}{}", req.state().upstream(), req.url().path());
|
||||
let token = header_as_str(&req, "Authorization", "");
|
||||
let accepts = header_as_str(&req, "Accept", "");
|
||||
|
||||
if let Ok(mut up_req) = make_get_request(&full_url, &token, &accepts).await {
|
||||
let manifest: models::Manifest = up_req.body_json().await.expect("Failed to parse response");
|
||||
debug!("Manifest: {:?}", manifest);
|
||||
Ok(Response::new(200))
|
||||
}
|
||||
else {
|
||||
error!("Failed to make an upstream request for {}", full_url);
|
||||
Ok(Response::new(500))
|
||||
}
|
||||
}
|
||||
else {
|
||||
error!("Override does not exist for {}/{}/{}, we should proxy upstream", org, image, digest);
|
||||
|
@ -204,11 +238,12 @@ fn main() -> Result<(), std::io::Error> {
|
|||
*/
|
||||
app.at("/v2/:org/:image/manifests/:digest").get(fetch_digest);
|
||||
/*
|
||||
* This route works handles images which look like "official" images,
|
||||
* This route handles images which look like "official" images,
|
||||
* such as `alpine:latest`, which _actually_ maps to `library/alpine:latest`
|
||||
* in DockerHub
|
||||
*/
|
||||
//app.at("/v2/:image/manifests/:digest").get(fetch_digest);
|
||||
|
||||
app.at("/v2/:org/:image/blobs/:sha").get(fetch_blob);
|
||||
/*
|
||||
* The catch-all for the remainder of the v2 API calls should proxy to
|
||||
|
|
|
@ -7,7 +7,6 @@ extern crate serde;
|
|||
extern crate serde_json;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Result;
|
||||
|
||||
/**
|
||||
* Manifest format, retrieved from: /v2/alpine/manifests/latest
|
||||
|
@ -51,8 +50,8 @@ use serde_json::Result;
|
|||
]
|
||||
}
|
||||
*/
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Manifest {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
#[serde(rename = "schemaVersion")]
|
||||
schema_version: u16,
|
||||
name: String,
|
||||
|
@ -63,14 +62,14 @@ struct Manifest {
|
|||
signatures: Vec<Signature>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Layer {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Layer {
|
||||
#[serde(rename = "blobSum")]
|
||||
blob_sum: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Signature {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Signature {
|
||||
signature: String,
|
||||
protected: String,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue