Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaro00 committed Aug 2, 2024
1 parent bd66722 commit dfc01f5
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 70 deletions.
87 changes: 18 additions & 69 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::net::{Ipv4Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::str::FromStr;
use std::sync::Arc;

use anyhow::{anyhow, bail, Context, Result};
use args::{ConfirmationArgs, GenerateCommand, TableArgs};
Expand All @@ -30,14 +31,14 @@ use futures::{StreamExt, TryFutureExt};
use git2::{Repository, StatusOptions};
use globset::{Glob, GlobSetBuilder};
use hyper::service::{make_service_fn, service_fn};
use hyper::{body, Body, Method, Request as HyperRequest, Response, Server};
use hyper::Server;
use ignore::overrides::OverrideBuilder;
use ignore::WalkBuilder;
use indicatif::ProgressBar;
use indoc::{formatdoc, printdoc};
use provisioner_server::beta::{handler, ProvApiState};
use reqwest::header::HeaderMap;
use shuttle_api_client::ShuttleApiClient;
use shuttle_common::resource::ProvisionResourceRequest;
use shuttle_common::{
constants::{
headers::X_CARGO_SHUTTLE_VERSION, API_URL_DEFAULT, DEFAULT_IDLE_MINUTES, EXAMPLES_REPO,
Expand Down Expand Up @@ -1820,6 +1821,7 @@ impl Shuttle {
}

async fn local_run_beta(&self, mut run_args: RunArgs) -> Result<CommandOutcome> {
let project_name = self.ctx.project_name().to_owned();
let services = self.pre_local_run(&run_args).await?;
let service = services
.first()
Expand All @@ -1842,76 +1844,23 @@ impl Shuttle {
Ipv4Addr::LOCALHOST
};

let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handler)) });

async fn handler(
req: HyperRequest<Body>,
) -> std::result::Result<Response<Body>, hyper::Error> {
let method = req.method().clone();
let uri = req.uri().clone();
debug!("Received {method} {uri}");

let body = body::to_bytes(req.into_body()).await?.to_vec();
let res = match (method, uri.to_string().as_str()) {
(Method::GET, "/projects/proj_LOCAL/resources/secrets") => resource::Response {
config: serde_json::Value::Null,
r#type: resource::Type::Secrets,
data: serde_json::to_value(HashMap::<String, String>::new()).unwrap(),
},
(Method::POST, "/projects/proj_LOCAL/resources") => {
let prov = LocalProvisioner::new().unwrap();
let shuttle_resource: ProvisionResourceRequest =
serde_json::from_slice(&body).unwrap();
// TODO: Reject req if version field mismatch

match shuttle_resource.r#type {
resource::Type::Database(db_type) => {
let config: DbInput = serde_json::from_value(shuttle_resource.config)
.context("deserializing resource config")?;
let res = match config.local_uri {
Some(local_uri) => DatabaseResource::ConnectionString(local_uri),
None => DatabaseResource::Info(
prov.provision_database(Request::new(DatabaseRequest {
project_name: project_name.to_string(),
db_type: Some(db_type.into()),
db_name: config.db_name,
}))
.await
.context("Failed to start database container. Make sure that a Docker engine is running.")?
.into_inner()
.into(),
),
};
resource::Response {
r#type: shuttle_resource.r#type,
config: serde_json::Value::Null,
data: serde_json::to_value(&res).unwrap(),
}
}
resource::Type::Container => {
let config = serde_json::from_value(shuttle_resource.config)
.context("deserializing resource config")?;
let res = prov.start_container(config).await.context("Failed to start Docker container. Make sure that a Docker engine is running.")?;
}
resource::Type::Secrets => {
panic!("bruh?");
}
_ => {
unimplemented!("resource not supported");
}
}
}
_ => todo!(),
};

let res = Response::new(Body::from(serde_json::to_vec(&res).unwrap()));

Ok(res)
}
let state = Arc::new(ProvApiState {
project_name,
secrets,
});
let make_svc = make_service_fn(move |_conn| {
let state = state.clone();
async {
Ok::<_, Infallible>(service_fn(move |req| {
let state = state.clone();
handler(state, req)
}))
}
});
let server = Server::bind(&api_addr).serve(make_svc);
tokio::spawn(async move {
if let Err(e) = server.await {
eprintln!("Server error: {}", e);
eprintln!("Provisioner server error: {}", e);
exit(1);
}
});
Expand Down
121 changes: 121 additions & 0 deletions cargo-shuttle/src/provisioner_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,124 @@ fn db_type_to_config(db_type: Type, database_name: &str) -> EngineConfig {
},
}
}

pub mod beta {
use std::{collections::HashMap, sync::Arc};

use anyhow::{bail, Context, Result};
use hyper::{body, Body, Method, Request as HyperRequest, Response};
use shuttle_common::{
resource::{self, ProvisionResourceRequest},
DatabaseResource, DbInput,
};
use shuttle_proto::provisioner::{provisioner_server::Provisioner, DatabaseRequest};
use shuttle_service::ShuttleResourceOutput;
use tonic::Request;
use tracing::debug;

use super::LocalProvisioner;

#[derive(Clone)]
pub struct ProvApiState {
pub project_name: String,
pub secrets: HashMap<String, String>,
}

pub async fn handler(
state: Arc<ProvApiState>,
req: HyperRequest<Body>,
) -> std::result::Result<Response<Body>, hyper::Error> {
let method = req.method().clone();
let uri = req.uri().clone();
debug!("Received {method} {uri}");

let body = body::to_bytes(req.into_body()).await?.to_vec();
let res = match provision(state, method, uri.to_string().as_str(), body).await {
Ok(bytes) => Response::new(Body::from(bytes)),
Err(e) => {
eprintln!("Encountered error when provisioning: {e}");
Response::builder().status(500).body(Body::empty()).unwrap()
}
};

Ok(res)
}

pub async fn provision(
state: Arc<ProvApiState>,
method: Method,
uri: &str,
body: Vec<u8>,
) -> Result<Vec<u8>> {
Ok(match (method, uri) {
(Method::GET, "/projects/proj_LOCAL/resources/secrets") => {
serde_json::to_vec(&resource::Response {
config: serde_json::Value::Null,
r#type: resource::Type::Secrets,
data: serde_json::to_value(&state.secrets).unwrap(),
})
.unwrap()
}
(Method::POST, "/projects/proj_LOCAL/resources") => {
let prov = LocalProvisioner::new().unwrap();
let shuttle_resource: ProvisionResourceRequest =
serde_json::from_slice(&body).context("deserializing resource request")?;
// TODO: Reject req if version field mismatch

let response = match shuttle_resource.r#type {
resource::Type::Database(db_type) => {
let config: DbInput = serde_json::from_value(shuttle_resource.config)
.context("deserializing resource config")?;
let res = match config.local_uri {
Some(local_uri) => DatabaseResource::ConnectionString(local_uri),
None => DatabaseResource::Info(
prov.provision_database(Request::new(DatabaseRequest {
project_name: state.project_name.clone(),
db_type: Some(db_type.into()),
db_name: config.db_name,
}))
.await
.context("Failed to start database container. Make sure that a Docker engine is running.")?
.into_inner()
.into(),
),
};
resource::Response {
r#type: shuttle_resource.r#type,
config: serde_json::Value::Null,
data: serde_json::to_value(&res).unwrap(),
}
}
resource::Type::Container => {
let config = serde_json::from_value(shuttle_resource.config)
.context("deserializing resource config")?;
let res = prov.start_container(config)
.await
.context("Failed to start Docker container. Make sure that a Docker engine is running.")?;
resource::Response {
r#type: shuttle_resource.r#type,
config: serde_json::Value::Null,
data: serde_json::to_value(&res).unwrap(),
}
}
resource::Type::Secrets => resource::Response {
r#type: shuttle_resource.r#type,
config: serde_json::Value::Null,
data: serde_json::to_value(&state.secrets).unwrap(),
},
_ => {
bail!("Resource not supported");
}
};

serde_json::to_vec(&ShuttleResourceOutput {
output: response,
custom: serde_json::Value::Null,
state: Some(resource::ResourceState::Ready),
})
.unwrap()
}
_ => bail!("Received unsupported resource request"),
})
}
}
4 changes: 3 additions & 1 deletion runtime/src/beta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub async fn start(loader: impl Loader + Send + 'static, runner: impl Runner + S
//
// LOADING / PROVISIONING PHASE
//
println!("Loading resources...");

let secrets: BTreeMap<String, String> = match client
.get_secrets_beta(&project_id)
Expand Down Expand Up @@ -159,7 +160,7 @@ pub async fn start(loader: impl Loader + Send + 'static, runner: impl Runner + S
*bytes = serde_json::to_vec(&secrets).expect("to serialize struct");
continue;
}
// TODO?: Add prints/tracing to show which resource is being provisioned
println!("Provisioning {}", shuttle_resource.r#type);
loop {
match client
.provision_resource_beta(&project_id, shuttle_resource.clone())
Expand Down Expand Up @@ -213,6 +214,7 @@ pub async fn start(loader: impl Loader + Send + 'static, runner: impl Runner + S
//
// RUNNING PHASE
//
println!("Starting service!");

if let Err(e) = service.bind(service_addr).await {
eprintln!("Service encountered an error in `bind`: {e}");
Expand Down

0 comments on commit dfc01f5

Please sign in to comment.