use std::path::Path;
use crate::collect::run::{found_on_path, Env, RunFailure, Runner};
use crate::collect::tracker::OpenFailure;
use crate::config::{Command, Project};
const PROBE: &str = "env -0";
pub const CREDENTIAL_VAR: &str = "BEADS_DOLT_PASSWORD";
pub const TRACKER_VAR: &str = "BEADS_DIR";
pub const NEVER_INHERITED: [&str; 2] = [CREDENTIAL_VAR, TRACKER_VAR];
pub fn ambient_credential() -> Option<String> {
std::env::var(CREDENTIAL_VAR).ok()
}
pub fn tracker_env(
runner: &dyn Runner,
project: &Project,
ambient: Option<&str>,
) -> Result<Env, OpenFailure> {
let mut env = ambient.map_or_else(Env::new, |password| {
Env::from([(CREDENTIAL_VAR.to_string(), password.to_string())])
});
if let Some(command) = project
.environment_command
.clone()
.or_else(|| detected(&project.path))
{
let captured =
entering(&project.path, runner, &command).map_err(|_| OpenFailure::NoEnvironment)?;
env.extend(captured);
}
if let Some(command) = &project.credential_command {
let password = runner
.run("sh", &["-c", command], Some(&project.path), &lending(&env))
.map_err(|_| OpenFailure::NoCredential)?;
env.insert(
CREDENTIAL_VAR.to_string(),
password.trim_end_matches(['\r', '\n']).to_string(),
);
}
Ok(env)
}
fn detected(path: &Path) -> Option<Command> {
(path.join(ENTERED_DIRECTORY).exists() && found_on_path(DIRENV, Some(path)))
.then(|| Command::Line(format!("{DIRENV} exec {THE_DIRECTORY_ITSELF}")))
}
const ENTERED_DIRECTORY: &str = ".envrc";
const DIRENV: &str = "direnv";
const THE_DIRECTORY_ITSELF: &str = ".";
fn lending(captured: &Env) -> Env {
let mut lent = captured.clone();
for withheld in NEVER_INHERITED {
lent.remove(withheld);
}
lent
}
fn entering(path: &Path, runner: &dyn Runner, command: &Command) -> Result<Env, RunFailure> {
let mut words = command.words().into_iter().chain(PROBE.split_whitespace());
let program = words.next().unwrap_or_default();
let argv: Vec<&str> = words.collect();
let out = runner.run(program, &argv, Some(path), &Env::new())?;
Ok(variables(&out))
}
fn variables(out: &str) -> Env {
out.split('\0')
.filter_map(|entry| {
let (named, value) = entry.split_once('=')?;
let name = named.rsplit('\n').next().unwrap_or(named);
(!name.is_empty()).then(|| (name.to_string(), value.to_string()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collect::run::testing::{every_failure_kind, FakeRunner};
use crate::collect::run::{FailureKind, RealRunner};
use std::path::PathBuf;
fn project_dir() -> PathBuf {
PathBuf::from("/nowhere/a-project")
}
fn ambient_project() -> Project {
Project {
name: "atlas".to_string(),
path: project_dir(),
environment_command: None,
credential_command: None,
poll: true,
badges: Vec::new(),
worktrees: Vec::new(),
}
}
const DIRENV: &str = "direnv exec .";
fn entered_with_direnv() -> Project {
Project {
environment_command: Some(Command::Line(DIRENV.to_string())),
..ambient_project()
}
}
fn credentialled() -> Env {
Env::from([(CREDENTIAL_VAR.to_string(), "hunter2".to_string())])
}
fn entering_the_directory() -> String {
format!("{DIRENV} {PROBE}")
}
fn exported(variables: &[(&str, &str)]) -> String {
variables
.iter()
.map(|(name, value)| format!("{name}={value}"))
.collect::<Vec<_>>()
.join("\0")
}
#[test]
fn a_project_asking_for_direnv_is_read_by_entering_its_directory() {
let runner = FakeRunner::default().with(
&entering_the_directory(),
&exported(&[
("BEADS_DIR", "/nowhere/a-project/.beads"),
("BEADS_DOLT_PASSWORD", "the-projects-own-password"),
]),
);
let env = tracker_env(&runner, &entered_with_direnv(), None).unwrap();
assert_eq!(
env.get("BEADS_DOLT_PASSWORD").map(String::as_str),
Some("the-projects-own-password"),
"the credential entering the directory produces did not reach bd"
);
assert_eq!(
env.get("BEADS_DIR").map(String::as_str),
Some("/nowhere/a-project/.beads"),
"the tracker entering the directory names did not reach bd"
);
}
#[test]
fn direnv_is_given_no_tracker_and_no_credential_of_bdis_own() {
let runner = FakeRunner::default().with(&entering_the_directory(), &exported(&[]));
tracker_env(
&runner,
&entered_with_direnv(),
Some("the-launching-shells-password"),
)
.unwrap();
assert!(
runner.call(&entering_the_directory()).env.is_empty(),
"direnv was handed an environment to reproduce"
);
}
#[test]
fn text_a_projects_envrc_wrote_first_does_not_lose_the_variable_behind_it() {
let noise = "entering the atlas shell\n";
let runner = FakeRunner::default().with(
&entering_the_directory(),
&format!(
"{noise}{}",
exported(&[("BEADS_DOLT_PASSWORD", "hunter2"), ("PATH", "/nix/bin")])
),
);
let env = tracker_env(&runner, &entered_with_direnv(), None).unwrap();
assert_eq!(env.get(CREDENTIAL_VAR).map(String::as_str), Some("hunter2"));
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/nix/bin"),
"the first variable was read as part of the text in front of it"
);
assert!(
!env.keys().any(|name| name.contains('\n')),
"text written before the variables became a variable: {env:?}"
);
}
#[test]
fn a_directory_that_cannot_be_entered_fails_the_project_rather_than_falling_back() {
let runner = FakeRunner::default().failing(
&entering_the_directory(),
RunFailure::unstartable("direnv", "No such file or directory"),
);
let failure = tracker_env(&runner, &entered_with_direnv(), Some("hunter2")).unwrap_err();
assert_eq!(failure, OpenFailure::NoEnvironment);
}
#[test]
fn every_way_the_capture_can_fail_is_the_same_failure_to_the_project() {
for kind in every_failure_kind() {
let runner = FakeRunner::default().failing(
&entering_the_directory(),
RunFailure {
kind,
program: "direnv".to_string(),
detail: "direnv did not produce an environment".to_string(),
unreadable: None,
},
);
let failure = tracker_env(&runner, &entered_with_direnv(), None).unwrap_err();
assert_eq!(failure, OpenFailure::NoEnvironment, "{kind:?}");
}
}
#[test]
fn a_project_with_no_environment_does_not_run_its_credential_command() {
let runner = FakeRunner::default().failing(
&entering_the_directory(),
RunFailure::unstartable("direnv", "No such file or directory"),
);
let project = Project {
credential_command: Some("op read the/password".to_string()),
..entered_with_direnv()
};
assert_eq!(
tracker_env(&runner, &project, None).unwrap_err(),
OpenFailure::NoEnvironment
);
}
#[test]
fn a_credential_command_gets_the_captured_tools_but_never_the_captured_password() {
let runner = FakeRunner::default()
.with(
&entering_the_directory(),
&exported(&[
("PATH", "/nix/bin"),
("BEADS_DOLT_PASSWORD", "the-projects-own-password"),
("BEADS_DIR", "/nowhere/a-project/.beads"),
]),
)
.with("sh -c op read the/password", "hunter2\n");
let project = Project {
credential_command: Some("op read the/password".to_string()),
..entered_with_direnv()
};
let env = tracker_env(&runner, &project, None).unwrap();
let call = runner.call("sh -c op read the/password");
assert_eq!(
call.env.get("PATH").map(String::as_str),
Some("/nix/bin"),
"the credential command could not reach the tools its own directory installs"
);
for withheld in NEVER_INHERITED {
assert!(
!call.env.contains_key(withheld),
"{withheld} reached the credential command"
);
}
assert_eq!(
env.get(CREDENTIAL_VAR).map(String::as_str),
Some("hunter2"),
"the credential command's answer did not win over the captured one"
);
}
#[test]
fn a_credential_command_answers_instead_of_entering_the_directory() {
let runner = FakeRunner::default().with("sh -c op read the/password", "hunter2\n");
let project = Project {
name: "atlas".to_string(),
path: project_dir(),
environment_command: None,
credential_command: Some("op read the/password".to_string()),
poll: true,
badges: Vec::new(),
worktrees: Vec::new(),
};
tracker_env(&runner, &project, None).unwrap();
assert!(
!runner
.calls()
.iter()
.any(|call| call.argv.starts_with("direnv ")),
"the directory was entered as well as the escape hatch being used"
);
}
#[test]
fn a_project_that_says_nothing_about_its_environment_is_read_without_running_anything() {
let runner = FakeRunner::default();
let env = tracker_env(&runner, &ambient_project(), Some("hunter2")).unwrap();
assert_eq!(env, credentialled());
assert_eq!(
runner.calls(),
Vec::new(),
"an ambient project ran a program to find its environment"
);
}
#[test]
fn a_wrapper_that_is_not_installed_is_named_rather_than_the_shell() {
let wrapper = Command::Line("no-such-wrapper-anywhere exec .".to_string());
let failure = entering(Path::new("."), &RealRunner, &wrapper).unwrap_err();
assert_eq!(failure.kind, FailureKind::NotInstalled);
assert_eq!(
failure.program, "no-such-wrapper-anywhere",
"the wrapper's own absence went on record as the shell's"
);
}
#[test]
fn the_variables_read_back_are_the_ones_env_wrote() {
let out = RealRunner
.run(
"env",
&["-0"],
None,
&Env::from([("K".to_string(), "v".to_string())]),
)
.expect("env runs");
let read = variables(&out);
assert_eq!(read.get("K").map(String::as_str), Some("v"));
assert_eq!(
read.len(),
out.split('\0').filter(|entry| !entry.is_empty()).count(),
"a variable env wrote was not read back"
);
}
#[test]
fn a_projects_credential_command_supplies_its_password() {
let runner = FakeRunner::default().with("sh -c op read the/password", "hunter2\n");
let project = Project {
name: "atlas".to_string(),
path: project_dir(),
environment_command: None,
credential_command: Some("op read the/password".to_string()),
poll: true,
badges: Vec::new(),
worktrees: Vec::new(),
};
let env = tracker_env(&runner, &project, Some("the-launching-shells-password")).unwrap();
assert_eq!(
env,
credentialled(),
"the trailing newline is not the password"
);
let call = runner.call("sh -c op read the/password");
assert_eq!(call.cwd.as_deref(), Some(project_dir().as_path()));
assert!(
call.env.is_empty(),
"the credential command gets no credential"
);
}
#[test]
fn a_project_with_no_credential_command_is_handed_the_ambient_credential() {
let runner = FakeRunner::default().with(
&entering_the_directory(),
&exported(&[("PATH", "/nix/bin")]),
);
let env = tracker_env(&runner, &entered_with_direnv(), Some("hunter2")).unwrap();
assert_eq!(env.get(CREDENTIAL_VAR).map(String::as_str), Some("hunter2"));
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/nix/bin"),
"the directory was entered but what it produced did not reach bd"
);
}
#[test]
fn a_project_with_no_credential_command_and_no_ambient_one_is_given_nothing() {
let runner = FakeRunner::default().with(&entering_the_directory(), &exported(&[]));
assert_eq!(
tracker_env(&runner, &entered_with_direnv(), None).unwrap(),
Env::new()
);
}
fn credentialled_by(command: &str) -> Project {
Project {
name: "atlas".to_string(),
path: project_dir(),
environment_command: None,
credential_command: Some(command.to_string()),
poll: true,
badges: Vec::new(),
worktrees: Vec::new(),
}
}
#[test]
fn every_way_a_credential_command_can_fail_is_the_same_failure_to_the_project() {
for kind in every_failure_kind() {
let runner = FakeRunner::default().failing(
"sh -c op read the/password",
RunFailure {
kind,
program: "sh".to_string(),
detail: "op: command not found".to_string(),
unreadable: None,
},
);
let failure =
tracker_env(&runner, &credentialled_by("op read the/password"), None).unwrap_err();
assert_eq!(failure, OpenFailure::NoCredential, "{kind:?}");
}
}
#[test]
fn a_credential_helper_this_machine_does_not_hold_arrives_as_the_fallthrough() {
let failure = RealRunner
.run(
"sh",
&["-c", "no-such-credential-helper-anywhere"],
Some(Path::new(".")),
&Env::new(),
)
.unwrap_err();
assert_eq!(failure.kind, FailureKind::Unavailable);
assert_eq!(failure.program, "sh");
}
}