refactor(sudo): add SudoExecuteOpts builder functions and preserve_env enum

This commit is contained in:
Andre Toerien
2025-07-16 00:11:20 +02:00
committed by Gideon
parent 306ff3c7c5
commit b6c1290934
4 changed files with 105 additions and 85 deletions

View File

@@ -127,15 +127,7 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
} else { } else {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() { if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
sudo.execute_opts( sudo.execute_opts(ctx, &gem, SudoExecuteOpts::new().preserve_env().set_home())?
ctx,
&gem,
SudoExecuteOpts {
preserve_env: Some(&[]),
set_home: true,
..Default::default()
},
)?
.args(["update", "--system"]) .args(["update", "--system"])
.status_checked()?; .status_checked()?;
} }

View File

@@ -993,14 +993,7 @@ pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
} }
print_separator(t!("Configuration update")); print_separator(t!("Configuration update"));
sudo.execute_opts( sudo.execute_opts(ctx, &pacdiff, SudoExecuteOpts::new().preserve_env_list(&["DIFFPROG"]))?
ctx,
&pacdiff,
SudoExecuteOpts {
preserve_env: Some(&["DIFFPROG"]),
..Default::default()
},
)?
.status_checked()?; .status_checked()?;
} }

View File

@@ -330,15 +330,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user)); print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
sudo.execute_opts( sudo.execute_opts(ctx, &binary_name, SudoExecuteOpts::new().set_home().user(&user.name))?
ctx,
&binary_name,
SudoExecuteOpts {
set_home: true,
user: Some(&user.name),
..Default::default()
},
)?
.current_dir("/tmp") // brew needs a writable current directory .current_dir("/tmp") // brew needs a writable current directory
.arg("update") .arg("update")
.status_checked()?; .status_checked()?;
@@ -557,14 +549,7 @@ pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
let nix_args = nix_args(); let nix_args = nix_args();
if multi_user { if multi_user {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
sudo.execute_opts( sudo.execute_opts(ctx, &nix, SudoExecuteOpts::new().interactive())?
ctx,
&nix,
SudoExecuteOpts {
interactive: true,
..Default::default()
},
)?
.args(nix_args) .args(nix_args)
.arg("upgrade-nix") .arg("upgrade-nix")
.status_checked() .status_checked()

View File

@@ -23,20 +23,72 @@ pub struct Sudo {
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
/// Generic sudo options, translated into flags to pass to `sudo`. Depending on the sudo kind, OS pub enum SudoPreserveEnv<'a> {
/// and system config, some options might be specified by default or unsupported. /// Preserve all environment variables.
All,
/// Preserve only the specified environment variables.
Some(&'a [&'a str]),
/// Preserve no environment variables.
#[default]
None,
}
/// Generic sudo options, translated into flags to pass to `sudo`.
/// NOTE: Depending on the sudo kind, OS and system config, some options might be specified by
/// default or unsupported.
#[derive(Clone, Debug, Default)]
pub struct SudoExecuteOpts<'a> { pub struct SudoExecuteOpts<'a> {
/// Run the command "interactively", i.e. inside a login shell. /// Run the command "interactively", i.e. inside a login shell.
pub interactive: bool, pub interactive: bool,
/// Preserve environment variables across the sudo call. If an empty list is given, preserves /// Preserve environment variables across the sudo call.
/// all existing environment variables. pub preserve_env: SudoPreserveEnv<'a>,
pub preserve_env: Option<&'a [&'a str]>,
/// Set the HOME environment variable to the target user's home directory. /// Set the HOME environment variable to the target user's home directory.
pub set_home: bool, pub set_home: bool,
/// Run the command as a user other than the root user. /// Run the command as a user other than the root user.
pub user: Option<&'a str>, pub user: Option<&'a str>,
} }
impl<'a> SudoExecuteOpts<'a> {
pub fn new() -> Self {
Self::default()
}
/// Run the command "interactively", i.e. inside a login shell.
#[allow(unused)]
pub fn interactive(mut self) -> Self {
self.interactive = true;
self
}
/// Preserve all environment variables across the sudo call.
#[allow(unused)]
pub fn preserve_env(mut self) -> Self {
self.preserve_env = SudoPreserveEnv::All;
self
}
/// Preserve only the specified environment variables across the sudo call.
#[allow(unused)]
pub fn preserve_env_list(mut self, vars: &'a [&'a str]) -> Self {
self.preserve_env = SudoPreserveEnv::Some(vars);
self
}
/// Set the HOME environment variable to the target user's home directory.
#[allow(unused)]
pub fn set_home(mut self) -> Self {
self.set_home = true;
self
}
/// Run the command as a user other than the root user.
#[allow(unused)]
pub fn user(mut self, user: &'a str) -> Self {
self.user = Some(user);
self
}
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
const DETECT_ORDER: [SudoKind; 5] = [ const DETECT_ORDER: [SudoKind; 5] = [
SudoKind::Doas, SudoKind::Doas,
@@ -143,7 +195,7 @@ impl Sudo {
/// Execute a command with `sudo`. /// Execute a command with `sudo`.
pub fn execute<S: AsRef<OsStr>>(&self, ctx: &ExecutionContext, command: S) -> Result<Executor> { pub fn execute<S: AsRef<OsStr>>(&self, ctx: &ExecutionContext, command: S) -> Result<Executor> {
self.execute_opts(ctx, command, SudoExecuteOpts::default()) self.execute_opts(ctx, command, SudoExecuteOpts::new())
} }
/// Execute a command with `sudo`, with custom options. /// Execute a command with `sudo`, with custom options.
@@ -182,9 +234,8 @@ impl Sudo {
cmd.arg("-d"); cmd.arg("-d");
} }
if let Some(preserve_env) = opts.preserve_env { match opts.preserve_env {
if preserve_env.is_empty() { SudoPreserveEnv::All => match self.kind {
match self.kind {
SudoKind::Sudo => { SudoKind::Sudo => {
cmd.arg("-E"); cmd.arg("-E");
} }
@@ -198,30 +249,29 @@ impl Sudo {
} }
.into()); .into());
} }
} },
} else { SudoPreserveEnv::Some(vars) => match self.kind {
match self.kind {
SudoKind::Sudo if cfg!(not(target_os = "windows")) => { SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
cmd.arg(format!("--preserve_env={}", preserve_env.join(","))); cmd.arg(format!("--preserve_env={}", vars.join(",")));
} }
SudoKind::Run0 => { SudoKind::Run0 => {
for env in preserve_env { for env in vars {
cmd.arg(format!("--setenv={}", env)); cmd.arg(format!("--setenv={}", env));
} }
} }
SudoKind::Please => { SudoKind::Please => {
cmd.arg("-a"); cmd.arg("-a");
cmd.arg(preserve_env.join(",")); cmd.arg(vars.join(","));
} }
SudoKind::Doas | SudoKind::Sudo | SudoKind::Gsudo | SudoKind::Pkexec => { SudoKind::Doas | SudoKind::Sudo | SudoKind::Gsudo | SudoKind::Pkexec => {
return Err(UnsupportedSudo { return Err(UnsupportedSudo {
sudo_kind: self.kind, sudo_kind: self.kind,
option: "preserve_env list", option: "preserve_env_list",
} }
.into()); .into());
} }
} },
} SudoPreserveEnv::None => {}
} }
if opts.set_home { if opts.set_home {