Add Sudo type (#221)

* Create `Sudo` type and `SudoKind` enum

* Fix build

* reformat

* Fix choco on windows

* Fix linux

* Fix linux more

* more fix stuff hehe hoho hahaha

* more fix stuff hehe hoho hahaha

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
This commit is contained in:
Rebecca Turner
2022-11-25 17:19:32 -05:00
committed by GitHub
parent 526c4c9a58
commit b31778bdd8
10 changed files with 157 additions and 89 deletions

View File

@@ -1,16 +1,17 @@
#![allow(dead_code)]
use crate::executor::RunType;
use crate::git::Git;
use crate::sudo::Sudo;
use crate::utils::require_option;
use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result;
use directories::BaseDirs;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::sync::Mutex;
pub struct ExecutionContext<'a> {
run_type: RunType,
sudo: &'a Option<PathBuf>,
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
@@ -23,7 +24,7 @@ pub struct ExecutionContext<'a> {
impl<'a> ExecutionContext<'a> {
pub fn new(
run_type: RunType,
sudo: &'a Option<PathBuf>,
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
@@ -40,18 +41,7 @@ impl<'a> ExecutionContext<'a> {
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
let sudo = require_option(self.sudo.clone(), "Sudo is required for this operation".into())?;
let mut cmd = self.run_type.execute(&sudo);
if sudo.ends_with("sudo") {
cmd.arg("--preserve-env=DIFFPROG");
}
if interactive {
cmd.arg("-i");
}
cmd.arg(command);
Ok(cmd)
Ok(sudo.execute_elevated(self, command, interactive))
}
pub fn run_type(&self) -> RunType {
@@ -62,8 +52,8 @@ impl<'a> ExecutionContext<'a> {
self.git
}
pub fn sudo(&self) -> &Option<PathBuf> {
self.sudo
pub fn sudo(&self) -> &Option<Sudo> {
&self.sudo
}
pub fn config(&self) -> &Config {

View File

@@ -83,10 +83,10 @@ fn run() -> Result<()> {
let git = git::Git::new();
let mut git_repos = git::Repositories::new(&git);
let sudo = sudo::path();
let sudo = sudo::Sudo::detect();
let run_type = executor::RunType::new(config.dry_run());
let ctx = execution_context::ExecutionContext::new(run_type, &sudo, &git, &config, &base_dirs);
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config, &base_dirs);
let mut runner = runner::Runner::new(&ctx);
@@ -121,7 +121,9 @@ fn run() -> Result<()> {
}
if config.pre_sudo() {
sudo::elevate(&ctx, sudo.as_ref())?;
if let Some(sudo) = ctx.sudo() {
sudo.elevate(&ctx)?;
}
}
let powershell = powershell::Powershell::new();
@@ -202,17 +204,17 @@ fn run() -> Result<()> {
#[cfg(target_os = "dragonfly")]
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
dragonfly::upgrade_packages(sudo.as_ref(), run_type)
dragonfly::upgrade_packages(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(target_os = "freebsd")]
runner.execute(Step::Pkg, "FreeBSD Packages", || {
freebsd::upgrade_packages(&ctx, sudo.as_ref(), run_type)
freebsd::upgrade_packages(&ctx, ctx.sudo().as_ref(), run_type)
})?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::Pkg, "OpenBSD Packages", || {
openbsd::upgrade_packages(sudo.as_ref(), run_type)
openbsd::upgrade_packages(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(target_os = "android")]
@@ -383,7 +385,7 @@ fn run() -> Result<()> {
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?;
runner.execute(Step::Snap, "snap", || linux::run_snap(sudo.as_ref(), run_type))?;
runner.execute(Step::Snap, "snap", || linux::run_snap(ctx.sudo().as_ref(), run_type))?;
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
@@ -403,11 +405,11 @@ fn run() -> Result<()> {
#[cfg(target_os = "linux")]
{
runner.execute(Step::System, "pihole", || {
linux::run_pihole_update(sudo.as_ref(), run_type)
linux::run_pihole_update(ctx.sudo().as_ref(), run_type)
})?;
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
runner.execute(Step::Restarts, "Restarts", || {
linux::run_needrestart(sudo.as_ref(), run_type)
linux::run_needrestart(ctx.sudo().as_ref(), run_type)
})?;
}
@@ -420,12 +422,12 @@ fn run() -> Result<()> {
#[cfg(target_os = "freebsd")]
runner.execute(Step::System, "FreeBSD Upgrade", || {
freebsd::upgrade_freebsd(sudo.as_ref(), run_type)
freebsd::upgrade_freebsd(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::System, "OpenBSD Upgrade", || {
openbsd::upgrade_openbsd(sudo.as_ref(), run_type)
openbsd::upgrade_openbsd(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(windows)]
@@ -457,10 +459,10 @@ fn run() -> Result<()> {
}
#[cfg(target_os = "freebsd")]
freebsd::audit_packages(&sudo).ok();
freebsd::audit_packages(ctx.sudo().as_ref()).ok();
#[cfg(target_os = "dragonfly")]
dragonfly::audit_packages(&sudo).ok();
dragonfly::audit_packages(ctx.sudo().as_ref()).ok();
}
let mut post_command_failed = false;

View File

@@ -202,7 +202,6 @@ pub fn run_juliaup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
}
run_type.execute(&juliaup).arg("update").status_checked()
}
pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {

View File

@@ -4,7 +4,6 @@ use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::process::Command;
use crate::sudo;
use crate::utils::require_option;
use color_eyre::eyre::Result;
#[cfg(target_os = "linux")]
@@ -13,9 +12,7 @@ use semver::Version;
use tracing::debug;
use crate::command::CommandExt;
use crate::executor::RunType;
use crate::terminal::print_separator;
use crate::utils::sudo;
use crate::utils::{require, PathExt};
use crate::{error::SkipStep, execution_context::ExecutionContext};
@@ -91,14 +88,17 @@ impl NPM {
Version::parse(&version_str?).map_err(|err| err.into())
}
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["update", self.global_location_arg()];
if use_sudo {
let sudo_option = sudo::path();
let sudo = require_option(sudo_option, String::from("sudo is not installed"))?;
run_type.execute(sudo).arg(&self.command).args(args).status_checked()?;
let sudo = require_option(ctx.sudo().clone(), String::from("sudo is not installed"))?;
ctx.run_type()
.execute(sudo)
.arg(&self.command)
.args(args)
.status_checked()?;
} else {
run_type.execute(&self.command).args(args).status_checked()?;
ctx.run_type().execute(&self.command).args(args).status_checked()?;
}
Ok(())
@@ -151,17 +151,18 @@ impl Yarn {
.map(|s| PathBuf::from(s.stdout.trim()))
}
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["global", "upgrade"];
if use_sudo {
run_type
.execute("sudo")
let sudo = require_option(ctx.sudo().clone(), String::from("sudo is not installed"))?;
ctx.run_type()
.execute(sudo)
.arg(self.yarn.as_ref().unwrap_or(&self.command))
.args(args)
.status_checked()?;
} else {
run_type.execute(&self.command).args(args).status_checked()?;
ctx.run_type().execute(&self.command).args(args).status_checked()?;
}
Ok(())
@@ -216,12 +217,12 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "linux")]
{
npm.upgrade(ctx.run_type(), should_use_sudo(&npm, ctx)?)
npm.upgrade(ctx, should_use_sudo(&npm, ctx)?)
}
#[cfg(not(target_os = "linux"))]
{
npm.upgrade(ctx.run_type(), false)
npm.upgrade(ctx, false)
}
}
@@ -232,12 +233,12 @@ pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "linux")]
{
pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?)
pnpm.upgrade(ctx, should_use_sudo(&pnpm, ctx)?)
}
#[cfg(not(target_os = "linux"))]
{
pnpm.upgrade(ctx.run_type(), false)
pnpm.upgrade(ctx, false)
}
}
@@ -253,12 +254,12 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "linux")]
{
yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?)
yarn.upgrade(ctx, should_use_sudo_yarn(&yarn, ctx)?)
}
#[cfg(not(target_os = "linux"))]
{
yarn.upgrade(ctx.run_type(), false)
yarn.upgrade(ctx, false)
}
}

View File

@@ -10,6 +10,7 @@ use walkdir::WalkDir;
use crate::command::CommandExt;
use crate::error::TopgradeError;
use crate::execution_context::ExecutionContext;
use crate::sudo::Sudo;
use crate::utils::which;
use crate::{config, Step};
@@ -110,7 +111,7 @@ impl Trizen {
}
pub struct Pacman {
sudo: PathBuf,
sudo: Sudo,
executable: PathBuf,
}
@@ -229,7 +230,7 @@ impl ArchPackageManager for Pamac {
pub struct Aura {
executable: PathBuf,
sudo: PathBuf,
sudo: Sudo,
}
impl Aura {

View File

@@ -1,12 +1,13 @@
use crate::command::CommandExt;
use crate::executor::RunType;
use crate::sudo::Sudo;
use crate::terminal::print_separator;
use crate::utils::require_option;
use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::process::Command;
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn upgrade_packages(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
print_separator("DragonFly BSD Packages");
run_type
@@ -15,7 +16,7 @@ pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()>
.status_checked()
}
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
pub fn audit_packages(sudo: Option<&Sudo>) -> Result<()> {
if let Some(sudo) = sudo {
println!();
Command::new(sudo)

View File

@@ -1,14 +1,14 @@
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::sudo::Sudo;
use crate::terminal::print_separator;
use crate::utils::require_option;
use crate::Step;
use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::process::Command;
pub fn upgrade_freebsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn upgrade_freebsd(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
print_separator("FreeBSD Update");
run_type
@@ -17,7 +17,7 @@ pub fn upgrade_freebsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()>
.status_checked()
}
pub fn upgrade_packages(ctx: &ExecutionContext, sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn upgrade_packages(ctx: &ExecutionContext, sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
print_separator("FreeBSD Packages");
@@ -30,7 +30,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext, sudo: Option<&PathBuf>, run_type
command.status_checked()
}
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
pub fn audit_packages(sudo: Option<&Sudo>) -> Result<()> {
if let Some(sudo) = sudo {
println!();
Command::new(sudo)

View File

@@ -10,6 +10,7 @@ use crate::error::{SkipStep, TopgradeError};
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::steps::os::archlinux;
use crate::sudo::Sudo;
use crate::terminal::{print_separator, print_warning};
use crate::utils::{require, require_option, which, PathExt};
use crate::Step;
@@ -498,7 +499,7 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_needrestart(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn run_needrestart(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
let needrestart = require("needrestart")?;
let distribution = Distribution::detect()?;
@@ -603,7 +604,7 @@ pub fn flatpak_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_snap(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn run_snap(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
let snap = require("snap")?;
@@ -615,7 +616,7 @@ pub fn run_snap(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
run_type.execute(sudo).arg(snap).arg("refresh").status_checked()
}
pub fn run_pihole_update(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
pub fn run_pihole_update(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
let pihole = require("pihole")?;
Path::new("/opt/pihole/update.sh").require()?;

View File

@@ -19,17 +19,16 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
print_separator("Chocolatey");
let mut cmd = &choco;
let mut args = vec!["upgrade", "all"];
if let Some(sudo) = ctx.sudo() {
cmd = sudo;
args.insert(0, "choco");
let mut command = match ctx.sudo() {
Some(sudo) => {
let mut command = ctx.run_type().execute(sudo);
command.arg(choco);
command
}
None => ctx.run_type().execute(choco),
};
let mut command = ctx.run_type().execute(cmd);
command.args(&args);
command.args(["upgrade", "all"]);
if yes {
command.arg("--yes");

View File

@@ -1,3 +1,5 @@
use std::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;
use color_eyre::eyre::Context;
@@ -5,30 +7,102 @@ use color_eyre::eyre::Result;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::Executor;
use crate::terminal::print_separator;
use crate::utils::which;
/// Get the path of the `sudo` utility.
///
/// Detects `doas`, `sudo`, `gsudo`, or `pkexec`.
pub fn path() -> Option<PathBuf> {
#[derive(Clone, Debug)]
pub struct Sudo {
/// The path to the `sudo` binary.
path: PathBuf,
/// The type of program being used as `sudo`.
kind: SudoKind,
}
impl Sudo {
/// Get the `sudo` binary for this platform.
pub fn detect() -> Option<Self> {
which("doas")
.or_else(|| which("sudo"))
.or_else(|| which("gsudo"))
.or_else(|| which("pkexec"))
.map(|p| (p, SudoKind::Doas))
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
.map(|(path, kind)| Self { path, kind })
}
/// Elevate permissions with `sudo`.
pub fn elevate(ctx: &ExecutionContext, sudo: Option<&PathBuf>) -> Result<()> {
if let Some(sudo) = sudo {
///
/// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a
/// step.
///
/// See: https://github.com/topgrade-rs/topgrade/issues/205
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
print_separator("Sudo");
ctx.run_type()
.execute(sudo)
// TODO: Does this work with `doas`, `pkexec`, `gsudo`, GNU `sudo`...?
.arg("-v")
.status_checked()
.wrap_err("Failed to elevate permissions")?;
let mut cmd = ctx.run_type().execute(self);
match self.kind {
SudoKind::Doas => {
// `doas` doesn't have anything like `sudo -v` to cache credentials,
// so we just execute a dummy `echo` command so we have something
// unobtrusive to run.
// See: https://man.openbsd.org/doas
cmd.arg("echo");
}
SudoKind::Sudo => {
// From `man sudo` on macOS:
// -v, --validate
// Update the user's cached credentials, authenticating the user
// if necessary. For the sudoers plugin, this extends the sudo
// timeout for another 5 minutes by default, but does not run a
// command. Not all security policies support cached credentials.
cmd.arg("-v");
}
SudoKind::Gsudo => {
// Shows current user, cache and console status.
// See: https://gerardog.github.io/gsudo/docs/usage
cmd.arg("status");
}
SudoKind::Pkexec => {
// I don't think this does anything; `pkexec` usually asks for
// authentication every time, although it can be configured
// differently.
//
// See the note for `doas` above.
//
// See: https://linux.die.net/man/1/pkexec
cmd.arg("echo");
}
}
cmd.status_checked().wrap_err("Failed to elevate permissions")
}
Ok(())
/// Execute a command with `sudo`.
pub fn execute_elevated(&self, ctx: &ExecutionContext, command: &Path, interactive: bool) -> Executor {
let mut cmd = ctx.run_type().execute(self);
if let SudoKind::Sudo = self.kind {
cmd.arg("--preserve-env=DIFFPROG");
}
if interactive {
cmd.arg("-i");
}
cmd.arg(command);
cmd
}
}
#[derive(Clone, Copy, Debug)]
enum SudoKind {
Doas,
Sudo,
Gsudo,
Pkexec,
}
impl AsRef<OsStr> for Sudo {
fn as_ref(&self) -> &OsStr {
self.path.as_ref()
}
}