mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
Autoenv rewrite, security and scripting (#2083)
* Add args in .nurc file to environment * Working dummy version * Add add_nurc to sync_env command * Parse .nurc file * Delete env vars after leaving directory * Removing vals not working, strangely * Refactoring, add comment * Debugging * Debug by logging to file * Add and remove env var behavior appears correct However, it does not use existing code that well. * Move work to cli.rs * Parse config directories * I am in a state of distress * Rename .nurc to .nu * Some notes for me * Refactoring * Removing vars works, but not done in a very nice fashion * Refactor env_vars_to_delete * Refactor env_vars_to_add() * Move directory environment code to separate file * Refactor from_config * Restore env values * Working? * Working? * Update comments and change var name * Formatting * Remove vars after leaving dir * Remove notes I made * Rename config function * Clippy * Cleanup and handle errors * cargo fmt * Better error messages, remove last (?) unwrap * FORMAT PLZ * Rename whitelisted_directories to allowed_directories * Add comment to clarify how overwritten values are restored. * Change list of allowed dirs to indexmap * Rewrite starting * rewrite everything * Overwritten env values tracks an indexmap instead of vector * Refactor restore function * Untrack removed vars properly * Performance concerns * Performance concerns * Error handling * Clippy * Add type aliases for String and OsString * Deletion almost works * Working? * Error handling and refactoring * nicer errors * Add TODO file * Move outside of loop * Error handling * Reworking adding of vars * Reworking adding of vars * Ready for testing * Refactoring * Restore overwritten vals code * todo.org * Remove overwritten values tracking, as it is not needed * Cleanup, stop tracking overwritten values as nu takes care of it * Init autoenv command * Initialize autoenv and autoenv trust * autoenv trust toml * toml * Use serde for autoenv * Optional directory arg * Add autoenv untrust command * ... actually add autoenv untrust this time * OsString and paths * Revert "OsString and paths" This reverts commite6eedf8824
. * Fix path * Fix path * Autoenv trust and untrust * Start using autoenv * Check hashes * Use trust functionality when setting vars * Remove unused code * Clippy * Nicer errors for autoenv commands * Non-working errors * Update error description * Satisfy fmt * Errors * Errors print, but not nicely * Nicer errors * fmt * Delete accidentally added todo.org file * Rename direnv to autoenv * Use ShellError instead of Error * Change tests to pass, danger zone? * Clippy and errors * Clippy... again * Replace match with or_else * Use sha2 crate for hashing * parsing and error msg * Refactoring * Only apply vars once * if parent dir * Delete vars * Rework exit code * Adding works * restore * Fix possibility of infinite loop * Refactoring * Non-working * Revert "Non-working" This reverts commite231b85570
. * Revert "Revert "Non-working"" This reverts commit804092e46a
. * Autoenv trust works without restart * Cargo fix * Script vars * Serde * Serde errors * Entry and exitscripts * Clippy * Support windows and handle errors * Formatting * Fix infinite loop on windows * Debugging windows loop * More windows infinite loop debugging * Windows loop debugging #3 * windows loop #4 * Don't return err * Cleanup unused code * Infinite loop debug * Loop debugging * Check if infinite loop is vars_to_add * env_vars_to_add does not terminate, skip loop as test * Hypothesis: std::env::current_dir() is messing with something * Hypothesis: std::env::current_dir() is messing with something * plz * make clippy happy * debugging in env_vars_to_add * Debbuging env_vars_to_add #2 * clippy * clippy.. * Fool clippy * Fix another infinite loop * Binary search for error location x) * Binary search #3 * fmt * Binary search #4 * more searching... * closing in... maybe * PLZ * Cleanup * Restore commented out functionality * Handle case when user gives the directory "." * fmt * Use fs::canonicalize for paths * Create optional script section * fmt * Add exitscripts even if no entryscripts are defined * All sections in .nu-env are now optional * Re-read config file each directory change * Hot reload after autoenv untrust, don't run exitscripts if untrusted * Debugging * Fix issue with recursive adding of vars * Thank you for finding my issues Mr. Azure * use std::env
This commit is contained in:
parent
9e82e5a2fa
commit
ee18f16378
64
Cargo.lock
generated
64
Cargo.lock
generated
@ -322,7 +322,16 @@ dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -634,6 +643,12 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
@ -866,7 +881,16 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1418,6 +1442,16 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.2.1"
|
||||
@ -2517,6 +2551,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"serde_yaml",
|
||||
"sha2",
|
||||
"shellexpand",
|
||||
"starship",
|
||||
"strip-ansi-escapes",
|
||||
@ -2969,6 +3004,12 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "1.4.0"
|
||||
@ -3892,10 +3933,23 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"digest",
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpuid-bool",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -75,6 +75,7 @@ serde_ini = "0.2.0"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
|
@ -352,6 +352,9 @@ pub fn create_default_context(
|
||||
whole_stream_command(Headers),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Autoenv),
|
||||
whole_stream_command(AutoenvTrust),
|
||||
whole_stream_command(AutoenvUnTrust),
|
||||
whole_stream_command(Math),
|
||||
whole_stream_command(MathAverage),
|
||||
whole_stream_command(MathMedian),
|
||||
|
@ -8,6 +8,9 @@ pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoenv;
|
||||
pub(crate) mod autoenv_trust;
|
||||
pub(crate) mod autoenv_untrust;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
@ -142,6 +145,9 @@ pub(crate) use command::{
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use calc::Calc;
|
||||
|
82
crates/nu-cli/src/commands/autoenv.rs
Normal file
82
crates/nu-cli/src/commands/autoenv.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
pub struct Autoenv;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct Trusted {
|
||||
pub files: IndexMap<String, Vec<u8>>,
|
||||
}
|
||||
impl Trusted {
|
||||
pub fn new() -> Self {
|
||||
Trusted {
|
||||
files: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn file_is_trusted(nu_env_file: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
|
||||
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
|
||||
let nufile = nu_env_file.to_str().unwrap_or("");
|
||||
|
||||
let trusted = read_trusted()?;
|
||||
Ok(trusted.files.get(nufile) == Some(&contentdigest))
|
||||
}
|
||||
|
||||
pub fn read_trusted() -> Result<Trusted, ShellError> {
|
||||
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(config_path)
|
||||
.or_else(|_| {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Couldn't open nu-env.toml",
|
||||
))
|
||||
})?;
|
||||
let mut doc = String::new();
|
||||
file.read_to_string(&mut doc)?;
|
||||
|
||||
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||
Ok(allowed)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoenv {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv"
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
|
||||
"Manage directory specific environments"
|
||||
}
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv")
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Allow .nu-env file in current directory",
|
||||
example: "autoenv trust",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
74
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
74
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::autoenv::read_trusted;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::SyntaxShape;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{fs, path::PathBuf};
|
||||
pub struct AutoenvTrust;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AutoenvTrust {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv trust"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv trust").optional("dir", SyntaxShape::String, "Directory to allow")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Trust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let file_to_trust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
}) => {
|
||||
let mut dir = fs::canonicalize(path)?;
|
||||
dir.push(".nu-env");
|
||||
dir
|
||||
}
|
||||
_ => {
|
||||
let mut dir = std::env::current_dir()?;
|
||||
dir.push(".nu-env");
|
||||
dir
|
||||
}
|
||||
};
|
||||
|
||||
let content = std::fs::read(&file_to_trust)?;
|
||||
|
||||
let filename = file_to_trust.to_string_lossy().to_string();
|
||||
let mut allowed = read_trusted()?;
|
||||
allowed
|
||||
.files
|
||||
.insert(filename, Sha256::digest(&content).as_slice().to_vec());
|
||||
|
||||
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||
let tomlstr = toml::to_string(&allowed).or_else(|_| {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Couldn't serialize allowed dirs to nu-env.toml",
|
||||
))
|
||||
})?;
|
||||
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(".nu-env trusted!").into_value(tag),
|
||||
)))
|
||||
}
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
98
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
98
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use super::autoenv::Trusted;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::SyntaxShape;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use std::io::Read;
|
||||
use std::{fs, path::PathBuf};
|
||||
pub struct AutoenvUnTrust;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AutoenvUnTrust {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv untrust"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv untrust").optional(
|
||||
"dir",
|
||||
SyntaxShape::String,
|
||||
"Directory to disallow",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Untrust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let file_to_untrust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
}) => {
|
||||
let mut dir = fs::canonicalize(path)?;
|
||||
dir.push(".nu-env");
|
||||
dir
|
||||
}
|
||||
_ => {
|
||||
let mut dir = std::env::current_dir()?;
|
||||
dir.push(".nu-env");
|
||||
dir
|
||||
}
|
||||
};
|
||||
|
||||
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||
|
||||
let mut file = match std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(config_path.clone())
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"Couldn't open nu-env.toml",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut doc = String::new();
|
||||
file.read_to_string(&mut doc)?;
|
||||
|
||||
let mut allowed: Trusted = toml::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||
|
||||
let file_to_untrust = file_to_untrust.to_string_lossy().to_string();
|
||||
|
||||
if allowed.files.remove(&file_to_untrust).is_none() {
|
||||
return
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"No .nu-env file to untrust in the given directory. Is it missing, or already untrusted?",
|
||||
));
|
||||
}
|
||||
|
||||
let tomlstr = toml::to_string(&allowed).or_else(|_| {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Couldn't serialize allowed dirs to nu-env.toml",
|
||||
))
|
||||
})?;
|
||||
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(".nu-env untrusted!").into_value(tag),
|
||||
)))
|
||||
}
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
@ -28,6 +28,10 @@ pub(crate) async fn run_internal_command(
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
if command.name == "autoenv untrust" {
|
||||
context.user_recently_used_autoenv_untrust = true;
|
||||
}
|
||||
|
||||
let result = {
|
||||
context
|
||||
.run_command(
|
||||
|
@ -75,6 +75,7 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
@ -88,6 +89,7 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
|
@ -76,6 +76,7 @@ pub struct Context {
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub raw_input: String,
|
||||
pub user_recently_used_autoenv_untrust: bool,
|
||||
pub(crate) shell_manager: ShellManager,
|
||||
|
||||
#[cfg(windows)]
|
||||
@ -96,6 +97,7 @@ impl Context {
|
||||
current_errors: raw_args.current_errors.clone(),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
@ -108,6 +110,7 @@ impl Context {
|
||||
current_errors: raw_args.current_errors.clone(),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
@ -122,6 +125,7 @@ impl Context {
|
||||
current_errors: args.current_errors.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
@ -133,6 +137,7 @@ impl Context {
|
||||
host: args.host.clone(),
|
||||
current_errors: args.current_errors.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
@ -151,6 +156,7 @@ impl Context {
|
||||
))),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: ShellManager::basic(registry)?,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
@ -166,6 +172,7 @@ impl Context {
|
||||
))),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: ShellManager::basic(registry)?,
|
||||
raw_input: String::default(),
|
||||
})
|
||||
|
@ -4,7 +4,6 @@ use std::fmt::Debug;
|
||||
pub trait Conf: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
fn nu_env_dirs(&self) -> Option<Value>;
|
||||
fn reload(&self);
|
||||
}
|
||||
|
||||
@ -13,10 +12,6 @@ impl Conf for Box<dyn Conf> {
|
||||
(**self).env()
|
||||
}
|
||||
|
||||
fn nu_env_dirs(&self) -> Option<Value> {
|
||||
(**self).nu_env_dirs()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
(**self).path()
|
||||
}
|
||||
|
@ -20,10 +20,6 @@ impl Conf for NuConfig {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn nu_env_dirs(&self) -> Option<Value> {
|
||||
self.nu_env_dirs()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
let mut vars = self.vars.lock();
|
||||
|
||||
@ -56,14 +52,6 @@ impl NuConfig {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn nu_env_dirs(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
if let Some(dirs) = vars.get("nu_env_dirs") {
|
||||
return Some(dirs.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
|
||||
|
@ -16,10 +16,6 @@ impl Conf for FakeConfig {
|
||||
self.config.env()
|
||||
}
|
||||
|
||||
fn nu_env_dirs(&self) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
self.config.path()
|
||||
}
|
||||
|
@ -1,192 +1,238 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Primitive, UntaggedValue, Value};
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use std::{ffi::OsString, fmt::Debug, path::PathBuf};
|
||||
use crate::commands;
|
||||
use commands::autoenv;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use nu_errors::ShellError;
|
||||
use serde::Deserialize;
|
||||
use std::cmp::Ordering::Less;
|
||||
use std::env::*;
|
||||
use std::process::Command;
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt::Debug,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
type EnvKey = String;
|
||||
type EnvVal = OsString;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DirectorySpecificEnvironment {
|
||||
allowed_directories: Vec<PathBuf>,
|
||||
pub last_seen_directory: PathBuf,
|
||||
//If an environment var has been added from a .nu in a directory, we track it here so we can remove it when the user leaves the directory.
|
||||
//If setting the var overwrote some value, we save the old value in an option so we can restore it later.
|
||||
added_env_vars: IndexMap<PathBuf, IndexMap<EnvKey, Option<EnvVal>>>,
|
||||
exitscripts: IndexMap<PathBuf, Vec<String>>,
|
||||
}
|
||||
|
||||
//Directory -> Env key. If an environment var has been added from a .nu in a directory, we track it here so we can remove it when the user leaves the directory.
|
||||
added_env_vars: IndexMap<PathBuf, Vec<String>>,
|
||||
|
||||
//Directory -> (env_key, value). If a .nu overwrites some existing environment variables, they are added here so that they can be restored later.
|
||||
overwritten_env_values: IndexMap<PathBuf, Vec<(String, OsString)>>,
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct NuEnvDoc {
|
||||
pub env: Option<IndexMap<String, String>>,
|
||||
pub scriptvars: Option<IndexMap<String, String>>,
|
||||
pub scripts: Option<IndexMap<String, Vec<String>>>,
|
||||
pub entryscripts: Option<Vec<String>>,
|
||||
pub exitscripts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl DirectorySpecificEnvironment {
|
||||
pub fn new(allowed_directories: Option<Value>) -> DirectorySpecificEnvironment {
|
||||
let mut allowed_directories = if let Some(Value {
|
||||
value: UntaggedValue::Table(ref wrapped_directories),
|
||||
..
|
||||
}) = allowed_directories
|
||||
{
|
||||
wrapped_directories
|
||||
.iter()
|
||||
.filter_map(|dirval| {
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref dir)),
|
||||
..
|
||||
} = dirval
|
||||
{
|
||||
return Some(PathBuf::from(&dir));
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
pub fn new() -> DirectorySpecificEnvironment {
|
||||
let root_dir = if cfg!(target_os = "windows") {
|
||||
PathBuf::from("c:\\")
|
||||
} else {
|
||||
vec![]
|
||||
PathBuf::from("/")
|
||||
};
|
||||
allowed_directories.sort();
|
||||
|
||||
DirectorySpecificEnvironment {
|
||||
allowed_directories,
|
||||
last_seen_directory: root_dir,
|
||||
added_env_vars: IndexMap::new(),
|
||||
overwritten_env_values: IndexMap::new(),
|
||||
exitscripts: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
//If we are no longer in a directory, we restore the values it overwrote.
|
||||
pub fn overwritten_values_to_restore(&mut self) -> Result<IndexMap<String, String>> {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
fn toml_if_directory_is_trusted(
|
||||
&mut self,
|
||||
nu_env_file: &PathBuf,
|
||||
) -> Result<NuEnvDoc, ShellError> {
|
||||
let content = std::fs::read(&nu_env_file)?;
|
||||
|
||||
let mut keyvals_to_restore = IndexMap::new();
|
||||
let mut new_overwritten = IndexMap::new();
|
||||
if autoenv::file_is_trusted(&nu_env_file, &content)? {
|
||||
let mut doc: NuEnvDoc = toml::de::from_slice(&content)
|
||||
.or_else(|e| Err(ShellError::untagged_runtime_error(format!("{:?}", e))))?;
|
||||
|
||||
for (directory, keyvals) in &self.overwritten_env_values {
|
||||
let mut working_dir = Some(current_dir.as_path());
|
||||
|
||||
let mut re_add_keyvals = true;
|
||||
while let Some(wdir) = working_dir {
|
||||
if wdir == directory.as_path() {
|
||||
re_add_keyvals = false;
|
||||
new_overwritten.insert(directory.clone(), keyvals.clone());
|
||||
break;
|
||||
} else {
|
||||
working_dir = working_dir //Keep going up in the directory structure with .parent()
|
||||
.ok_or_else(|| {
|
||||
Error::new(ErrorKind::NotFound, "Root directory has no parent")
|
||||
})?
|
||||
.parent();
|
||||
}
|
||||
}
|
||||
if re_add_keyvals {
|
||||
for (k, v) in keyvals {
|
||||
keyvals_to_restore.insert(
|
||||
k.clone(),
|
||||
v.to_str()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("{:?} is not valid unicode", v),
|
||||
)
|
||||
})?
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.overwritten_env_values = new_overwritten;
|
||||
Ok(keyvals_to_restore)
|
||||
}
|
||||
|
||||
pub fn env_vars_to_add(&mut self) -> Result<IndexMap<String, String>> {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
|
||||
let mut vars_to_add = IndexMap::new();
|
||||
for dir in &self.allowed_directories {
|
||||
let mut working_dir = Some(current_dir.as_path());
|
||||
|
||||
//Start in the current directory, then traverse towards the root with working_dir to see if we are in a subdirectory of a valid directory.
|
||||
while let Some(wdir) = working_dir {
|
||||
if wdir == dir.as_path() {
|
||||
let toml_doc = std::fs::read_to_string(wdir.join(".nu-env").as_path())?
|
||||
.parse::<toml::Value>()?;
|
||||
|
||||
let vars_in_current_file = toml_doc
|
||||
.get("env")
|
||||
.ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"No [env] section in .nu-env",
|
||||
)
|
||||
})?
|
||||
.as_table()
|
||||
.ok_or_else(|| {
|
||||
Error::new(ErrorKind::InvalidData, "Malformed [env] section in .nu-env")
|
||||
})?;
|
||||
|
||||
let mut keys_in_current_nufile = vec![];
|
||||
for (k, v) in vars_in_current_file {
|
||||
vars_to_add.insert(
|
||||
k.clone(),
|
||||
v.as_str()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Could not read environment variable: {}", v),
|
||||
)
|
||||
})?
|
||||
.to_string(),
|
||||
); //This is used to add variables to the environment
|
||||
keys_in_current_nufile.push(k.clone()); //this is used to keep track of which directory added which variables
|
||||
if let Some(scripts) = doc.scripts.as_ref() {
|
||||
for (k, v) in scripts {
|
||||
if k == "entryscripts" {
|
||||
doc.entryscripts = Some(v.clone());
|
||||
} else if k == "exitscripts" {
|
||||
doc.exitscripts = Some(v.clone());
|
||||
}
|
||||
|
||||
//If we are about to overwrite any environment variables, we save them first so they can be restored later.
|
||||
self.overwritten_env_values.insert(
|
||||
wdir.to_path_buf(),
|
||||
keys_in_current_nufile
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
if let Some(val) = std::env::var_os(key) {
|
||||
return Some((key.clone(), val));
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
self.added_env_vars
|
||||
.insert(wdir.to_path_buf(), keys_in_current_nufile);
|
||||
break;
|
||||
} else {
|
||||
working_dir = working_dir //Keep going up in the directory structure with .parent()
|
||||
.ok_or_else(|| {
|
||||
Error::new(ErrorKind::NotFound, "Root directory has no parent")
|
||||
})?
|
||||
.parent();
|
||||
}
|
||||
}
|
||||
return Ok(doc);
|
||||
}
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
format!("{:?} is untrusted. Run 'autoenv trust {:?}' to trust it.\nThis needs to be done after each change to the file.", nu_env_file, nu_env_file.parent().unwrap_or_else(|| &Path::new("")))))
|
||||
}
|
||||
|
||||
pub fn env_vars_to_add(&mut self) -> Result<IndexMap<EnvKey, EnvVal>, ShellError> {
|
||||
let mut dir = current_dir()?;
|
||||
let mut vars_to_add: IndexMap<EnvKey, EnvVal> = IndexMap::new();
|
||||
|
||||
//If we are in the last seen directory, do nothing
|
||||
//If we are in a parent directory to last_seen_directory, just return without applying .nu-env in the parent directory - they were already applied earlier.
|
||||
//parent.cmp(child) = Less
|
||||
let mut popped = true;
|
||||
while self.last_seen_directory.cmp(&dir) == Less && popped {
|
||||
let nu_env_file = dir.join(".nu-env");
|
||||
if nu_env_file.exists() {
|
||||
let nu_env_doc = self.toml_if_directory_is_trusted(&nu_env_file)?;
|
||||
|
||||
//add regular variables from the [env section]
|
||||
if let Some(env) = nu_env_doc.env {
|
||||
for (env_key, env_val) in env {
|
||||
self.add_key_if_appropriate(&mut vars_to_add, &dir, &env_key, &env_val);
|
||||
}
|
||||
}
|
||||
|
||||
//Add variables that need to evaluate scripts to run, from [scriptvars] section
|
||||
if let Some(scriptvars) = nu_env_doc.scriptvars {
|
||||
for (env_key, dir_val_script) in scriptvars {
|
||||
let command = if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", dir_val_script.as_str()])
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(&dir_val_script).output()?
|
||||
};
|
||||
if command.stdout.is_empty() {
|
||||
return Err(ShellError::untagged_runtime_error(format!(
|
||||
"{:?} in {:?} did not return any output",
|
||||
dir_val_script, dir
|
||||
)));
|
||||
}
|
||||
let response =
|
||||
std::str::from_utf8(&command.stdout[..command.stdout.len() - 1])
|
||||
.or_else(|e| {
|
||||
Err(ShellError::untagged_runtime_error(format!(
|
||||
"Couldn't parse stdout from command {:?}: {:?}",
|
||||
command, e
|
||||
)))
|
||||
})?;
|
||||
self.add_key_if_appropriate(
|
||||
&mut vars_to_add,
|
||||
&dir,
|
||||
&env_key,
|
||||
&response.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(entryscripts) = nu_env_doc.entryscripts {
|
||||
for script in entryscripts {
|
||||
if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", script.as_str()])
|
||||
.output()?;
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(script).output()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exitscripts) = nu_env_doc.exitscripts {
|
||||
self.exitscripts.insert(dir.clone(), exitscripts);
|
||||
}
|
||||
}
|
||||
popped = dir.pop();
|
||||
}
|
||||
|
||||
Ok(vars_to_add)
|
||||
}
|
||||
|
||||
//If the user has left directories which added env vars through .nu, we clear those vars
|
||||
pub fn env_vars_to_delete(&mut self) -> Result<Vec<String>> {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
pub fn add_key_if_appropriate(
|
||||
&mut self,
|
||||
vars_to_add: &mut IndexMap<EnvKey, EnvVal>,
|
||||
dir: &PathBuf,
|
||||
env_key: &str,
|
||||
env_val: &str,
|
||||
) {
|
||||
//This condition is to make sure variables in parent directories don't overwrite variables set by subdirectories.
|
||||
if !vars_to_add.contains_key(env_key) {
|
||||
vars_to_add.insert(env_key.to_string(), OsString::from(env_val));
|
||||
self.added_env_vars
|
||||
.entry(dir.clone())
|
||||
.or_insert(IndexMap::new())
|
||||
.insert(env_key.to_string(), var_os(env_key));
|
||||
}
|
||||
}
|
||||
|
||||
//Gather up all environment variables that should be deleted.
|
||||
//If we are not in a directory or one of its subdirectories, mark the env_vals it maps to for removal.
|
||||
let vars_to_delete = self.added_env_vars.iter().fold(
|
||||
Vec::new(),
|
||||
|mut vars_to_delete, (directory, env_vars)| {
|
||||
let mut working_dir = Some(current_dir.as_path());
|
||||
pub fn cleanup_after_dir_exit(
|
||||
&mut self,
|
||||
) -> Result<IndexMap<EnvKey, Option<EnvVal>>, ShellError> {
|
||||
let current_dir = current_dir()?;
|
||||
let mut vars_to_cleanup = IndexMap::new();
|
||||
|
||||
while let Some(wdir) = working_dir {
|
||||
if wdir == directory {
|
||||
return vars_to_delete;
|
||||
//If we are in the same directory as last_seen, or a subdirectory to it, do nothing
|
||||
//If we are in a subdirectory to last seen, do nothing
|
||||
//If we are in a parent directory to last seen, exit .nu-envs from last seen to parent and restore old vals
|
||||
let mut dir = self.last_seen_directory.clone();
|
||||
|
||||
let mut popped = true;
|
||||
while current_dir.cmp(&dir) == Less && popped {
|
||||
if let Some(vars_added_by_this_directory) = self.added_env_vars.get(&dir) {
|
||||
for (k, v) in vars_added_by_this_directory {
|
||||
vars_to_cleanup.insert(k.clone(), v.clone());
|
||||
}
|
||||
self.added_env_vars.remove(&dir);
|
||||
}
|
||||
|
||||
if let Some(scripts) = self.exitscripts.get(&dir) {
|
||||
for script in scripts {
|
||||
if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", script.as_str()])
|
||||
.output()?;
|
||||
} else {
|
||||
working_dir = working_dir.expect("Root directory has no parent").parent();
|
||||
Command::new("sh").arg("-c").arg(script).output()?;
|
||||
}
|
||||
}
|
||||
//only delete vars from directories we are not in
|
||||
vars_to_delete.extend(env_vars.clone());
|
||||
vars_to_delete
|
||||
},
|
||||
);
|
||||
}
|
||||
popped = dir.pop();
|
||||
}
|
||||
Ok(vars_to_cleanup)
|
||||
}
|
||||
|
||||
Ok(vars_to_delete)
|
||||
// If the user recently ran autoenv untrust on a file, we clear the environment variables it set and make sure to not run any possible exitscripts.
|
||||
pub fn clear_recently_untrusted_file(&mut self) -> Result<(), ShellError> {
|
||||
// Figure out which file was untrusted
|
||||
// Remove all vars set by it
|
||||
let current_trusted_files: IndexSet<PathBuf> = autoenv::read_trusted()?
|
||||
.files
|
||||
.iter()
|
||||
.map(|(k, _)| PathBuf::from(k))
|
||||
.collect();
|
||||
|
||||
// We figure out which file(s) the user untrusted by taking the set difference of current trusted files in .config/nu/nu-env.toml and the files tracked by self.added_env_vars
|
||||
// If a file is in self.added_env_vars but not in nu-env.toml, it was just untrusted.
|
||||
let untrusted_files: IndexSet<PathBuf> = self
|
||||
.added_env_vars
|
||||
.iter()
|
||||
.filter_map(|(path, _)| {
|
||||
if !current_trusted_files.contains(path) {
|
||||
return Some(path.clone());
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
for path in untrusted_files {
|
||||
if let Some(added_keys) = self.added_env_vars.get(&path) {
|
||||
for (key, _) in added_keys {
|
||||
remove_var(key);
|
||||
}
|
||||
}
|
||||
self.exitscripts.remove(&path);
|
||||
self.added_env_vars.remove(&path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
65
crates/nu-cli/src/env/environment.rs
vendored
65
crates/nu-cli/src/env/environment.rs
vendored
@ -1,15 +1,18 @@
|
||||
use crate::data::config::Conf;
|
||||
use crate::env::directory_specific_environment::*;
|
||||
use indexmap::{indexmap, IndexSet};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::env::*;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Env: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool);
|
||||
fn add_env(&mut self, key: &str, value: &str);
|
||||
fn add_path(&mut self, new_path: OsString);
|
||||
}
|
||||
|
||||
@ -22,8 +25,8 @@ impl Env for Box<dyn Env> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool) {
|
||||
(**self).add_env(key, value, overwrite_existing);
|
||||
fn add_env(&mut self, key: &str, value: &str) {
|
||||
(**self).add_env(key, value);
|
||||
}
|
||||
|
||||
fn add_path(&mut self, new_path: OsString) {
|
||||
@ -35,7 +38,7 @@ impl Env for Box<dyn Env> {
|
||||
pub struct Environment {
|
||||
environment_vars: Option<Value>,
|
||||
path_vars: Option<Value>,
|
||||
pub direnv: DirectorySpecificEnvironment,
|
||||
pub autoenv: DirectorySpecificEnvironment,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
@ -43,49 +46,41 @@ impl Environment {
|
||||
Environment {
|
||||
environment_vars: None,
|
||||
path_vars: None,
|
||||
direnv: DirectorySpecificEnvironment::new(None),
|
||||
autoenv: DirectorySpecificEnvironment::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
|
||||
let env = configuration.env();
|
||||
let path = configuration.path();
|
||||
|
||||
Environment {
|
||||
environment_vars: env,
|
||||
path_vars: path,
|
||||
direnv: DirectorySpecificEnvironment::new(configuration.nu_env_dirs()),
|
||||
autoenv: DirectorySpecificEnvironment::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maintain_directory_environment(&mut self) -> std::io::Result<()> {
|
||||
self.direnv.env_vars_to_delete()?.iter().for_each(|k| {
|
||||
self.remove_env(&k);
|
||||
});
|
||||
self.direnv.env_vars_to_add()?.iter().for_each(|(k, v)| {
|
||||
self.add_env(&k, &v, true);
|
||||
});
|
||||
pub fn autoenv(&mut self, reload_trusted: bool) -> Result<(), ShellError> {
|
||||
for (k, v) in self.autoenv.env_vars_to_add()? {
|
||||
set_var(&k, OsString::from(v.to_string_lossy().to_string()));
|
||||
}
|
||||
|
||||
self.direnv
|
||||
.overwritten_values_to_restore()?
|
||||
.iter()
|
||||
.for_each(|(k, v)| {
|
||||
self.add_env(&k, &v, true);
|
||||
});
|
||||
for (k, v) in self.autoenv.cleanup_after_dir_exit()? {
|
||||
if let Some(v) = v {
|
||||
set_var(k, v);
|
||||
} else {
|
||||
remove_var(k);
|
||||
}
|
||||
}
|
||||
|
||||
if reload_trusted {
|
||||
self.autoenv.clear_recently_untrusted_file()?;
|
||||
}
|
||||
|
||||
self.autoenv.last_seen_directory = current_dir()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_env(&mut self, key: &str) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Row(ref mut envs),
|
||||
..
|
||||
}) = self.environment_vars
|
||||
{
|
||||
envs.entries.remove(key);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn morph<T: Conf>(&mut self, configuration: &T) {
|
||||
self.environment_vars = configuration.env();
|
||||
self.path_vars = configuration.path();
|
||||
@ -109,7 +104,7 @@ impl Env for Environment {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool) {
|
||||
fn add_env(&mut self, key: &str, value: &str) {
|
||||
let value = UntaggedValue::string(value);
|
||||
|
||||
let new_envs = {
|
||||
@ -120,7 +115,7 @@ impl Env for Environment {
|
||||
{
|
||||
let mut new_envs = envs.clone();
|
||||
|
||||
if !new_envs.contains_key(key) || overwrite_existing {
|
||||
if !new_envs.contains_key(key) {
|
||||
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
|
||||
}
|
||||
|
||||
@ -146,7 +141,7 @@ impl Env for Environment {
|
||||
{
|
||||
let mut new_paths = current_paths.clone();
|
||||
|
||||
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
|
||||
let new_path_candidates = split_paths(&paths).map(|path| {
|
||||
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
|
||||
});
|
||||
|
||||
@ -238,7 +233,7 @@ mod tests {
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = Environment::from_config(&fake_config);
|
||||
|
||||
actual.add_env("USER", "NUNO", false);
|
||||
actual.add_env("USER", "NUNO");
|
||||
|
||||
assert_eq!(
|
||||
actual.env(),
|
||||
@ -271,7 +266,7 @@ mod tests {
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = Environment::from_config(&fake_config);
|
||||
|
||||
actual.add_env("SHELL", "/usr/bin/sh", false);
|
||||
actual.add_env("SHELL", "/usr/bin/sh");
|
||||
|
||||
assert_eq!(
|
||||
actual.env(),
|
||||
|
84
crates/nu-cli/src/env/environment_syncer.rs
vendored
84
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,6 +1,8 @@
|
||||
use crate::context::Context;
|
||||
use crate::data::config::{Conf, NuConfig};
|
||||
|
||||
use crate::env::environment::{Env, Environment};
|
||||
use nu_source::Text;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -44,14 +46,16 @@ impl EnvironmentSyncer {
|
||||
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
|
||||
crate::cli::print_err(e, &Text::from(""));
|
||||
}
|
||||
ctx.user_recently_used_autoenv_untrust = false;
|
||||
if environment.env().is_some() {
|
||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||
if name != "path" && name != "PATH" {
|
||||
// account for new env vars present in the current session
|
||||
// that aren't loaded from config.
|
||||
environment.add_env(&name, &value, false);
|
||||
|
||||
environment.maintain_directory_environment().ok();
|
||||
environment.add_env(&name, &value);
|
||||
|
||||
// clear the env var from the session
|
||||
// we are about to replace them
|
||||
@ -126,6 +130,7 @@ mod tests {
|
||||
use crate::context::Context;
|
||||
use crate::data::config::tests::FakeConfig;
|
||||
use crate::env::environment::Env;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
@ -139,13 +144,12 @@ mod tests {
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
|
||||
let expected = vec![
|
||||
(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
),
|
||||
("USER".to_string(), "NUNO".to_string()),
|
||||
];
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
);
|
||||
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||
|
||||
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
@ -203,33 +207,34 @@ mod tests {
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
let actual = vec![
|
||||
("SHELL".to_string(), var_shell),
|
||||
("USER".to_string(), var_user),
|
||||
];
|
||||
let mut found = IndexMap::new();
|
||||
found.insert("SHELL".to_string(), var_shell);
|
||||
found.insert("USER".to_string(), var_user);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
for k in found.keys() {
|
||||
assert!(expected.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
// Now confirm in-memory environment variables synced appropriately
|
||||
// including the newer one accounted for.
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let vars = environment
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vars, expected);
|
||||
);
|
||||
});
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -238,10 +243,11 @@ mod tests {
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
|
||||
let expected = vec![(
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
)];
|
||||
);
|
||||
|
||||
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
@ -278,26 +284,30 @@ mod tests {
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
let actual = vec![("SHELL".to_string(), var_shell)];
|
||||
let mut found = IndexMap::new();
|
||||
found.insert("SHELL".to_string(), var_shell);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
for k in found.keys() {
|
||||
assert!(expected.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let vars = environment
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vars, expected);
|
||||
value.as_string().expect("couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
1
crates/nu-cli/tests/commands/autoenv.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv.rs
Normal file
@ -0,0 +1 @@
|
||||
|
1
crates/nu-cli/tests/commands/autoenv_trust.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv_trust.rs
Normal file
@ -0,0 +1 @@
|
||||
|
1
crates/nu-cli/tests/commands/autoenv_untrust.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv_untrust.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,5 +1,8 @@
|
||||
mod alias;
|
||||
mod append;
|
||||
mod autoenv;
|
||||
mod autoenv_trust;
|
||||
mod autoenv_untrust;
|
||||
mod cal;
|
||||
mod calc;
|
||||
mod cd;
|
||||
|
Loading…
Reference in New Issue
Block a user