Skip to content

Commit

Permalink
fix: Populate pythonic of environment vars
Browse files Browse the repository at this point in the history
  • Loading branch information
bradhe committed Nov 23, 2024
1 parent 6ff66eb commit 175c713
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
13 changes: 8 additions & 5 deletions crates/tower-cmd/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ pub async fn do_run(config: Config, client: Client, args: &ArgMatches, cmd: Opti
/// the package, and launch the app. The relevant package is cleaned up after execution is
/// complete.
async fn do_run_local(_config: Config, client: Client, path: PathBuf, mut params: HashMap<String, String>) {
// There is always an implicit `local` environment when running in a local context.
let env = "local".to_string();

// Load all the secrets from the server
let secrets = get_secrets(&client).await;
let secrets = get_secrets(&client, &env).await;

// Load the Towerfile
let towerfile_path = path.join("Towerfile");
Expand All @@ -84,8 +87,9 @@ async fn do_run_local(_config: Config, client: Client, path: PathBuf, mut params
std::process::exit(1);
}


let mut launcher: AppLauncher<LocalApp> = AppLauncher::default();
if let Err(err) = launcher.launch(package, secrets, params).await {
if let Err(err) = launcher.launch(package, env, secrets, params).await {
output::runtime_error(err);
return;
}
Expand Down Expand Up @@ -177,10 +181,9 @@ fn resolve_path(cmd: Option<(&str, &ArgMatches)>) -> PathBuf {

/// get_secrets manages the process of getting secrets from the Tower server in a way that can be
/// used by the local runtime during local app execution.
async fn get_secrets(client: &Client) -> HashMap<String, String> {
let env = "local".to_string();
async fn get_secrets(client: &Client, env: &str) -> HashMap<String, String> {
let mut spinner = output::spinner("Getting secrets...");
match client.export_secrets(false, Some(env)).await {
match client.export_secrets(false, Some(env.to_string())).await {
Ok(secrets) => {
spinner.success();
secrets.into_iter().map(|sec| (sec.name, sec.value)).collect()
Expand Down
3 changes: 3 additions & 0 deletions crates/tower-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ impl<A: App> AppLauncher<A> {
pub async fn launch(
&mut self,
package: Package,
environment: String,
secrets: HashMap<String, String>,
parameters: HashMap<String, String>,
) -> Result<(), Error> {
let cwd = package.unpacked_path.clone().unwrap().to_path_buf();

let opts = StartOptions {
cwd: Some(cwd),
environment,
secrets,
parameters,
package,
Expand Down Expand Up @@ -108,6 +110,7 @@ impl<A: App> AppLauncher<A> {
pub struct StartOptions {
pub package: Package,
pub cwd: Option<PathBuf>,
pub environment: String,
pub secrets: HashMap<String, String>,
pub parameters: HashMap<String, String>,
}
Expand Down
63 changes: 56 additions & 7 deletions crates/tower-runtime/src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ async fn find_bash() -> Result<PathBuf, Error> {
impl App for LocalApp {
async fn start(opts: StartOptions) -> Result<Self, Error> {
let package = opts.package;
let environment = opts.environment;
let package_path = package.unpacked_path
.clone()
.unwrap()
Expand All @@ -156,6 +157,8 @@ impl App for LocalApp {
current_dir().unwrap()
};

let mut is_virtualenv = false;

if Path::new(&package_path.join("requirements.txt")).exists() {
log::debug!("requirements.txt file found. installing dependencies");

Expand Down Expand Up @@ -184,6 +187,8 @@ impl App for LocalApp {
python_path = find_python(Some(working_dir.join(".venv").join("bin"))).await?;
log::debug!("using virtualenv python at {:?}", python_path);

is_virtualenv = true;

let res = Command::new(pip_path)
.current_dir(&working_dir)
.arg("install")
Expand All @@ -207,13 +212,13 @@ impl App for LocalApp {
let secrets = opts.secrets;
let params= opts.parameters;

Self::execute_bash_program(working_dir, package_path, &manifest, secrets, params).await
Self::execute_bash_program(&environment, working_dir, is_virtualenv, package_path, &manifest, secrets, params).await
} else {
let manifest = &package.manifest;
let secrets = opts.secrets;
let params= opts.parameters;

Self::execute_python_program(working_dir, python_path, package_path, &manifest, secrets, params).await
Self::execute_python_program(&environment, working_dir, is_virtualenv, python_path, package_path, &manifest, secrets, params).await
};

if let Ok(child) = res {
Expand Down Expand Up @@ -297,7 +302,16 @@ impl App for LocalApp {
}

impl LocalApp {
async fn execute_python_program(cwd: PathBuf, python_path: PathBuf, package_path: PathBuf, manifest: &Manifest, secrets: HashMap<String, String>, params: HashMap<String, String>) -> Result<Child, Error> {
async fn execute_python_program(
env: &str,
cwd: PathBuf,
is_virtualenv: bool,
python_path: PathBuf,
package_path: PathBuf,
manifest: &Manifest,
secrets: HashMap<String, String>,
params: HashMap<String, String>,
) -> Result<Child, Error> {
log::debug!(" - python script {}", manifest.invoke);

let child = Command::new(python_path)
Expand All @@ -307,14 +321,22 @@ impl LocalApp {
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.envs(make_env_vars(&secrets, &params))
.envs(make_env_vars(env, &cwd, is_virtualenv, &secrets, &params))
.kill_on_drop(true)
.spawn()?;

Ok(child)
}

async fn execute_bash_program(cwd: PathBuf, package_path: PathBuf, manifest: &Manifest, secrets: HashMap<String, String>, params: HashMap<String, String>) -> Result<Child, Error> {
async fn execute_bash_program(
env: &str,
cwd: PathBuf,
is_virtualenv: bool,
package_path: PathBuf,
manifest: &Manifest,
secrets: HashMap<String, String>,
params: HashMap<String, String>,
) -> Result<Child, Error> {
let bash_path = find_bash().await?;
log::debug!("using bash at {:?}", bash_path);

Expand All @@ -326,7 +348,7 @@ impl LocalApp {
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.envs(make_env_vars(&secrets, &params))
.envs(make_env_vars(env, &cwd, is_virtualenv, &secrets, &params))
.kill_on_drop(true)
.spawn()?;

Expand All @@ -343,7 +365,7 @@ fn make_env_var_key(src: &str) -> String {
}
}

fn make_env_vars(secs: &HashMap<String, String>, params: &HashMap<String, String>) -> HashMap<String, String> {
fn make_env_vars(env: &str, cwd: &PathBuf, is_virtualenv: bool, secs: &HashMap<String, String>, params: &HashMap<String, String>) -> HashMap<String, String> {
let mut res = HashMap::new();

log::debug!("converting {} env variables", (params.len() + secs.len()));
Expand All @@ -358,6 +380,33 @@ fn make_env_vars(secs: &HashMap<String, String>, params: &HashMap<String, String
res.insert(key.to_string(), value.to_string());
}

// If we're in a virtual environment, we need to add the bin directory to the PATH so that we
// can find any executables that were installed there.
if is_virtualenv {
let venv_path = cwd.join(".venv")
.join("bin")
.to_string_lossy()
.to_string();

if let Ok(path) = std::env::var("PATH") {
res.insert("PATH".to_string(), format!("{}:{}", venv_path, path));
} else {
res.insert("PATH".to_string(), venv_path);
}
}

// We also need a PYTHONPATH that is set to the current working directory to help with the
// dependency resolution problem at runtime.
let pythonpath = cwd.to_string_lossy().to_string();
res.insert("PYTHONPATH".to_string(), pythonpath);

// Inject a TOWER_ENVIRONMENT parameter so you know what environment you're running in. Empty
// environment is "default" by default.
if env.is_empty() {
res.insert("TOWER_ENVIRONMENT".to_string(), "default".to_string());
} else {
res.insert("TOWER_ENVIRONMENT".to_string(), env.to_string());
}

res
}
Expand Down

0 comments on commit 175c713

Please sign in to comment.