Skip to content

Commit

Permalink
feat: embed runtime into client and deployer (#559)
Browse files Browse the repository at this point in the history
* refactor: runtime startup code

* feat: hook cli to runtime

* feat: hook up logs

* feat: custom port

* feat: start a next project with the local runner

* feat: embed executable

* refactor: axum update

* refactor: tonic version from workspace

* refactor: kill runtime correctly

* feat: DB resources for local runs

* feat: static folders for local runs

* feat: secrets for local runner

* refactor: cleanup logs and errors

* refactor: rebuild runtime if it changed

* refactor: more comments

* feat: minimal axum logs

* refactor: unneeded default features

* refactor: fix rerun-if for runtime

* bug: codegen handle_request

* refactor: restore db error
  • Loading branch information
chesedo authored Jan 6, 2023
1 parent 9db7f90 commit c34d5e4
Show file tree
Hide file tree
Showing 44 changed files with 912 additions and 464 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ uuid = "1.2.2"
thiserror = "1.0.37"
serde = "1.0.148"
serde_json = "1.0.89"
tonic = "0.8.3"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
4 changes: 4 additions & 0 deletions cargo-shuttle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ tokio = { version = "1.22.0", features = ["macros"] }
tokio-tungstenite = { version = "0.17.2", features = ["native-tls"] }
toml = "0.5.9"
toml_edit = "0.15.0"
tonic = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
url = "2.3.1"
Expand All @@ -53,6 +54,9 @@ webbrowser = "0.8.2"
workspace = true
features= ["models"]

[dependencies.shuttle-proto]
workspace = true

[dependencies.shuttle-secrets]
version = "0.8.0"
path = "../resources/secrets"
Expand Down
17 changes: 17 additions & 0 deletions cargo-shuttle/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::{env, process::Command};

fn main() {
println!("cargo:rerun-if-changed=../runtime");

// Build binary for runtime so that it can be embedded in the binary for the cli
let out_dir = env::var_os("OUT_DIR").unwrap();
Command::new("cargo")
.arg("build")
.arg("--package")
.arg("shuttle-runtime")
.arg("--target-dir")
.arg(out_dir)
.arg("--release")
.output()
.expect("failed to build the shuttle runtime");
}
143 changes: 100 additions & 43 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
mod args;
mod client;
pub mod config;
mod factory;
mod init;
mod provisioner_server;

use shuttle_common::project::ProjectName;
use std::collections::BTreeMap;
use shuttle_proto::runtime::{self, LoadRequest, StartRequest, SubscribeLogsRequest};
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::{read_to_string, File};
use std::io::stdout;
Expand All @@ -21,26 +22,26 @@ use clap_complete::{generate, Shell};
use config::RequestContext;
use crossterm::style::Stylize;
use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Input, Password};
use factory::LocalFactory;
use flate2::write::GzEncoder;
use flate2::Compression;
use futures::StreamExt;
use futures::{StreamExt, TryFutureExt};
use git2::{Repository, StatusOptions};
use ignore::overrides::OverrideBuilder;
use ignore::WalkBuilder;
use provisioner_server::LocalProvisioner;
use shuttle_common::models::{project, secret};
use shuttle_service::loader::{build_crate, Loader, Runtime};
use shuttle_service::Logger;
use shuttle_service::loader::{build_crate, Runtime};
use std::fmt::Write;
use strum::IntoEnumIterator;
use tar::Builder;
use tokio::sync::mpsc;
use tracing::trace;
use uuid::Uuid;

use crate::args::{DeploymentCommand, ProjectCommand};
use crate::client::Client;

const BINARY_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/release/shuttle-runtime"));

pub struct Shuttle {
ctx: RequestContext,
}
Expand Down Expand Up @@ -392,60 +393,116 @@ impl Shuttle {
"Building".bold().green(),
working_directory.display()
);
let so_path = match build_crate(id, working_directory, false, false, tx).await? {
Runtime::Legacy(path) => path,
Runtime::Next(_) => todo!(),
};
let runtime = build_crate(id, working_directory, false, tx).await?;

trace!("loading secrets");
let secrets_path = working_directory.join("Secrets.toml");

let secrets: BTreeMap<String, String> =
if let Ok(secrets_str) = read_to_string(secrets_path) {
let secrets: BTreeMap<String, String> =
secrets_str.parse::<toml::Value>()?.try_into()?;
let secrets: HashMap<String, String> = if let Ok(secrets_str) = read_to_string(secrets_path)
{
let secrets: HashMap<String, String> =
secrets_str.parse::<toml::Value>()?.try_into()?;

trace!(keys = ?secrets.keys(), "available secrets");
trace!(keys = ?secrets.keys(), "available secrets");

secrets
} else {
trace!("no Secrets.toml was found");
Default::default()
};
secrets
} else {
trace!("no Secrets.toml was found");
Default::default()
};

let service_name = self.ctx.project_name().to_string();

let loader = Loader::from_so_file(so_path)?;
let (is_wasm, so_path) = match runtime {
Runtime::Next(path) => (true, path),
Runtime::Legacy(path) => (false, path),
};

let mut factory = LocalFactory::new(
self.ctx.project_name().clone(),
let provisioner = LocalProvisioner::new()?;
let provisioner_server = provisioner.start(SocketAddr::new(
Ipv4Addr::LOCALHOST.into(),
run_args.port + 1,
));
let (mut runtime, mut runtime_client) = runtime::start(
BINARY_BYTES,
is_wasm,
runtime::StorageManagerType::WorkingDir(working_directory.to_path_buf()),
&format!("http://localhost:{}", run_args.port + 1),
)
.await
.map_err(|err| {
provisioner_server.abort();

err
})?;

let load_request = tonic::Request::new(LoadRequest {
path: so_path
.into_os_string()
.into_string()
.expect("to convert path to string"),
service_name: service_name.clone(),
secrets,
working_directory.to_path_buf(),
)?;
let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), run_args.port);
});
trace!("loading service");
let _ = runtime_client
.load(load_request)
.or_else(|err| async {
provisioner_server.abort();
runtime.kill().await?;

Err(err)
})
.await?;

trace!("loading project");
println!(
"\n{:>12} {} on http://{}",
"Starting".bold().green(),
self.ctx.project_name(),
addr
);
let (tx, mut rx) = mpsc::unbounded_channel();
let mut stream = runtime_client
.subscribe_logs(tonic::Request::new(SubscribeLogsRequest {}))
.or_else(|err| async {
provisioner_server.abort();
runtime.kill().await?;

Err(err)
})
.await?
.into_inner();

tokio::spawn(async move {
while let Some(log) = rx.recv().await {
while let Some(log) = stream.message().await.expect("to get log from stream") {
let log: shuttle_common::LogItem = log.into();
println!("{log}");
}
});

let logger = Logger::new(tx, id);
let (handle, so) = loader.load(&mut factory, addr, logger).await?;
let start_request = StartRequest {
deployment_id: id.as_bytes().to_vec(),
service_name,
port: run_args.port as u32,
};

handle.await??;
trace!(?start_request, "starting service");
let response = runtime_client
.start(tonic::Request::new(start_request))
.or_else(|err| async {
provisioner_server.abort();
runtime.kill().await?;

tokio::task::spawn_blocking(move || {
trace!("closing so file");
so.close().unwrap();
});
Err(err)
})
.await?
.into_inner();

trace!(response = ?response, "client response: ");

let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), run_args.port);

println!(
"\n{:>12} {} on http://{}",
"Starting".bold().green(),
self.ctx.project_name(),
addr
);

runtime.wait().await?;

Ok(())
}
Expand Down
Loading

0 comments on commit c34d5e4

Please sign in to comment.