Compare commits
2 commits
10d47dc51c
...
f4f135da71
| Author | SHA1 | Date | |
|---|---|---|---|
| f4f135da71 | |||
| 866b5b5164 |
9 changed files with 230 additions and 45 deletions
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
set -eu
|
||||
|
||||
./build.sh "$1" && clear
|
||||
./build.sh "$1"
|
||||
clear
|
||||
./run.sh "$1"
|
||||
|
|
|
|||
|
|
@ -10,11 +10,9 @@ if podman container exists "$tag"; then
|
|||
podman stop --time 3 "$tag"
|
||||
fi
|
||||
|
||||
if ! [ -f "../target/debug/$binary" ]; then
|
||||
cd ..
|
||||
cargo build
|
||||
cd -
|
||||
fi
|
||||
cd ..
|
||||
cargo build
|
||||
cd -
|
||||
|
||||
cp -v ../target/debug/$binary $binary
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ fail() { printf " [FAIL] %b\n" "$1"; exit 1; }
|
|||
|
||||
try() {
|
||||
actual="$1"
|
||||
expected="$2"
|
||||
expected="$(printf '%b' "$2")"
|
||||
fail_message="${3:-}"
|
||||
ok_message="${4:-}"
|
||||
|
||||
|
|
@ -20,6 +20,8 @@ try() {
|
|||
fi
|
||||
}
|
||||
|
||||
info "tori version $(tori version)"
|
||||
|
||||
announce "Fresh install has no manually installed packages"
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" ""
|
||||
|
|
@ -28,11 +30,13 @@ info "Updating apt packages"
|
|||
apt-get update >/dev/null
|
||||
|
||||
announce "Manually installed package is the only package in 'tori manual'"
|
||||
info "Installing: figlet"
|
||||
apt-get install -y figlet >/dev/null 2>&1
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" figlet
|
||||
|
||||
announce "Manually installed packages are the only packages in 'tori manual'"
|
||||
info "Installing: sudo"
|
||||
apt-get install -y sudo >/dev/null 2>&1
|
||||
tori_manual=$(tori manual | sort)
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "$(printf 'figlet\nsudo')"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ fail() { printf " [FAIL] %b\n" "$1"; exit 1; }
|
|||
|
||||
try() {
|
||||
actual="$1"
|
||||
expected="$2"
|
||||
expected="$(printf '%b' "$2")"
|
||||
operator="${3:-=}"
|
||||
fail_message="${3:-}"
|
||||
ok_message="${4:-}"
|
||||
|
|
@ -23,18 +23,43 @@ try() {
|
|||
fi
|
||||
}
|
||||
|
||||
info "tori version $(tori version)"
|
||||
|
||||
announce "sudo works"
|
||||
whoami=$(whoami)
|
||||
sudo_whoami=$(sudo whoami)
|
||||
echo try "$whoami" "$sudo_whoami" !=
|
||||
try "$whoami" "$sudo_whoami" !=
|
||||
echo try "$sudo_whoami" root
|
||||
try "$sudo_whoami" root
|
||||
|
||||
info "Updating apt packages"
|
||||
sudo apt-get update >/dev/null
|
||||
|
||||
announce "Manually installed packages are the only packages in 'tori manual'"
|
||||
info "Installing: sudo"
|
||||
sudo apt-get install -y sudo >/dev/null 2>&1
|
||||
tori_manual=$(tori manual | sort)
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "sudo"
|
||||
|
||||
announce "Manually installed packages change after installing one"
|
||||
info "Installing: figlet"
|
||||
sudo apt-get install -y figlet >/dev/null 2>&1
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "figlet\nsudo"
|
||||
|
||||
announce "Manually installed packages change after installing several"
|
||||
info "Installing: vim-tiny tmux qalc"
|
||||
sudo apt-get install -y vim-tiny tmux qalc >/dev/null 2>&1
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "figlet\nqalc\nsudo\ntmux\nvim-tiny"
|
||||
|
||||
announce "Manually installed packages change after uninstalling one"
|
||||
info "Uninstalling: qalc"
|
||||
sudo apt-get remove -y qalc >/dev/null 2>&1
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "figlet\nsudo\ntmux\nvim-tiny"
|
||||
|
||||
announce "Manually installed packages change after uninstalling several"
|
||||
info "Uninstalling: figlet tmux vim-tiny"
|
||||
sudo apt-get remove -y figlet tmux vim-tiny >/dev/null 2>&1
|
||||
tori_manual=$(tori manual)
|
||||
try "$tori_manual" "sudo"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
Kind, OperatingSystem,
|
||||
pkg::{self, Package, PackagerVariant, Packages},
|
||||
},
|
||||
run::{Command, executor::read},
|
||||
run::{Command, Transaction, TransactionCommand, executor::read},
|
||||
};
|
||||
|
||||
pub const DEBIAN: OperatingSystem = OperatingSystem {
|
||||
|
|
@ -25,12 +25,20 @@ pub struct Apt {
|
|||
}
|
||||
|
||||
impl Packages for Apt {
|
||||
fn install(&self, packages: &[Package], config: &Configuration) -> Result<(), pkg::Error> {
|
||||
super::debian::Apt::haul("install", packages, config)
|
||||
fn install(
|
||||
&self,
|
||||
packages: &[Package],
|
||||
config: &Configuration,
|
||||
) -> Result<Transaction, pkg::Error> {
|
||||
super::debian::Apt::haul(&Operation::Install, packages, config)
|
||||
}
|
||||
|
||||
fn uninstall(&self, packages: &[Package], config: &Configuration) -> Result<(), pkg::Error> {
|
||||
super::debian::Apt::haul("remove", packages, config)
|
||||
fn uninstall(
|
||||
&self,
|
||||
packages: &[Package],
|
||||
config: &Configuration,
|
||||
) -> Result<Transaction, pkg::Error> {
|
||||
super::debian::Apt::haul(&Operation::Uninstall, packages, config)
|
||||
}
|
||||
|
||||
fn manual(&self) -> Result<Vec<Package>, pkg::Error> {
|
||||
|
|
@ -145,20 +153,64 @@ impl Packages for Apt {
|
|||
|
||||
impl Apt {
|
||||
fn haul(
|
||||
subcommand: &str,
|
||||
operation: &Operation,
|
||||
packages: &[Package],
|
||||
config: &Configuration,
|
||||
) -> Result<(), pkg::Error> {
|
||||
) -> Result<Transaction, pkg::Error> {
|
||||
if packages.is_empty() {
|
||||
println!("Package selection is empty: Nothing to {subcommand}");
|
||||
return Ok(());
|
||||
println!("Package selection is empty: Nothing to {operation}");
|
||||
return Ok(Transaction::default());
|
||||
}
|
||||
|
||||
let args: Vec<&str> = iter::once(subcommand)
|
||||
// TODO This works as it is stated and is interesting as part of the
|
||||
// PoC, but doesn't really make sense to install something that wasn't
|
||||
// installed in the first place as the "rollback" of a failed uninstall
|
||||
let rollback_operation = match operation {
|
||||
Operation::Install => Operation::Uninstall,
|
||||
Operation::Uninstall => Operation::Install,
|
||||
};
|
||||
|
||||
let run_args: Vec<&str> = iter::once(operation.into())
|
||||
.chain(packages.iter().map(|p| p.into()))
|
||||
.collect();
|
||||
|
||||
let command = Command::new("apt", &args).escalate(config)?;
|
||||
Ok(crate::run::executor::spawn(&command)?)
|
||||
let rollback_args: Vec<&str> = iter::once(rollback_operation.into())
|
||||
.chain(packages.iter().map(|p| p.into()))
|
||||
.collect();
|
||||
|
||||
let run = Command::new("apt", &run_args).escalate(config)?;
|
||||
let rollback = Command::new("apt", &rollback_args).escalate(config)?;
|
||||
let transaction_command = TransactionCommand::new(run, rollback);
|
||||
Ok(Transaction::single(&transaction_command))
|
||||
}
|
||||
}
|
||||
|
||||
enum Operation {
|
||||
Install,
|
||||
Uninstall,
|
||||
}
|
||||
|
||||
impl<'s> From<Operation> for &'s str {
|
||||
fn from(operation: Operation) -> &'s str {
|
||||
match operation {
|
||||
Operation::Install => "install",
|
||||
Operation::Uninstall => "remove",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> From<&'s Operation> for &'s str {
|
||||
fn from(operation: &Operation) -> &str {
|
||||
match *operation {
|
||||
Operation::Install => "install",
|
||||
Operation::Uninstall => "remove",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Operation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s: &str = self.into();
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@ use core::{
|
|||
fmt::Debug,
|
||||
};
|
||||
|
||||
use crate::{conf::Configuration, os::debian, run};
|
||||
use crate::{
|
||||
conf::Configuration,
|
||||
os::debian,
|
||||
run::{self, Transaction},
|
||||
};
|
||||
|
||||
pub trait Packages: Clone + Default + Debug + PartialEq + Eq {
|
||||
fn install(&self, packages: &[Package], config: &Configuration) -> Result<(), Error>;
|
||||
fn uninstall(&self, packages: &[Package], config: &Configuration) -> Result<(), Error>;
|
||||
fn install(&self, packages: &[Package], config: &Configuration) -> Result<Transaction, Error>;
|
||||
fn uninstall(&self, packages: &[Package], config: &Configuration)
|
||||
-> Result<Transaction, Error>;
|
||||
fn manual(&self) -> Result<Vec<Package>, Error>;
|
||||
fn automatic(&self) -> Result<Vec<Package>, Error>;
|
||||
fn variant(&self) -> &PackagerVariant;
|
||||
|
|
@ -23,14 +28,18 @@ pub enum Packager {
|
|||
}
|
||||
|
||||
impl Packages for Packager {
|
||||
fn install(&self, packages: &[Package], config: &Configuration) -> Result<(), Error> {
|
||||
fn install(&self, packages: &[Package], config: &Configuration) -> Result<Transaction, Error> {
|
||||
match self {
|
||||
Packager::Apt(p) => p.install(packages, config),
|
||||
Packager::Unknown => Error::unknown_packager(&format!("install {packages:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn uninstall(&self, packages: &[Package], config: &Configuration) -> Result<(), Error> {
|
||||
fn uninstall(
|
||||
&self,
|
||||
packages: &[Package],
|
||||
config: &Configuration,
|
||||
) -> Result<Transaction, Error> {
|
||||
match self {
|
||||
Packager::Apt(p) => p.uninstall(packages, config),
|
||||
Packager::Unknown => Error::unknown_packager(&format!("uninstall {packages:?}")),
|
||||
|
|
|
|||
58
src/run.rs
58
src/run.rs
|
|
@ -74,6 +74,7 @@ pub enum TaskKind {
|
|||
pub struct Command {
|
||||
pub base: String,
|
||||
pub args: Vec<String>,
|
||||
escalated: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
|
|
@ -104,6 +105,7 @@ impl Command {
|
|||
|
||||
Ok(Command {
|
||||
base: config.su_command.command().clone().base,
|
||||
escalated: true,
|
||||
args,
|
||||
})
|
||||
}
|
||||
|
|
@ -112,17 +114,69 @@ impl Command {
|
|||
Command {
|
||||
base: base.to_string(),
|
||||
args: args.iter().map(|e| e.to_string()).collect(),
|
||||
escalated: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn escalated(&self) -> bool {
|
||||
self.escalated
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct Transaction {
|
||||
commands: Vec<TransactionCommand>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn single(command: &TransactionCommand) -> Transaction {
|
||||
Transaction {
|
||||
commands: vec![command.clone()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct TransactionCommand {
|
||||
run: Command,
|
||||
rollback: Command,
|
||||
status: TransactionCommandStatus,
|
||||
errors: Option<Vec<executor::Error>>,
|
||||
}
|
||||
|
||||
impl TransactionCommand {
|
||||
pub const fn new(run: Command, rollback: Command) -> TransactionCommand {
|
||||
TransactionCommand {
|
||||
run,
|
||||
rollback,
|
||||
status: TransactionCommandStatus::Pending,
|
||||
errors: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_error(&mut self, error: &executor::Error) {
|
||||
self.errors.get_or_insert_with(Vec::new).push(error.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub enum TransactionCommandStatus {
|
||||
#[default]
|
||||
Pending,
|
||||
Success,
|
||||
PendingRollback,
|
||||
Rolledback,
|
||||
FailedRollback,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
pub kind: ErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
BadSuCommandConfig,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,41 @@
|
|||
use std::process;
|
||||
|
||||
use crate::{os::pkg::Package, run::Command};
|
||||
use crate::{
|
||||
log::elog,
|
||||
os::pkg::Package,
|
||||
run::{Command, Transaction, TransactionCommandStatus},
|
||||
};
|
||||
|
||||
pub mod meta;
|
||||
|
||||
pub fn print(message: &str) -> Result<(), Error> {
|
||||
println!("{message}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_packages(packages: Vec<Package>) -> Result<(), Error> {
|
||||
for package in packages {
|
||||
print(&format!("{package}"))?;
|
||||
// TODO Should this be a method of Transaction instead?
|
||||
pub fn commit(transaction: &mut Transaction) -> Result<(), Error> {
|
||||
elog(&format!("Committing transaction: {transaction:#?}"));
|
||||
for command in &mut transaction.commands {
|
||||
if let Err(error) = spawn(&command.run) {
|
||||
command.status = TransactionCommandStatus::PendingRollback;
|
||||
command.push_error(&error);
|
||||
if let Err(rollback_error) = spawn(&command.rollback) {
|
||||
command.status = TransactionCommandStatus::FailedRollback;
|
||||
command.push_error(&rollback_error);
|
||||
elog(&format!("Failed rollback of command {:#?}", &command));
|
||||
return Err(rollback_error);
|
||||
} else {
|
||||
command.status = TransactionCommandStatus::Rolledback;
|
||||
elog(&format!("Successfully rolled back command {:#?}", &command));
|
||||
return Err(error);
|
||||
}
|
||||
} else {
|
||||
command.status = TransactionCommandStatus::Success;
|
||||
elog(&format!("Successfully ran command {:#?}", &command));
|
||||
}
|
||||
}
|
||||
|
||||
elog("Transaction committed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn spawn(command: &Command) -> Result<(), Error> {
|
||||
pub(super) fn spawn(command: &Command) -> Result<(), Error> {
|
||||
if let Ok(mut child) = process::Command::new(&command.base)
|
||||
.args(&command.args)
|
||||
.spawn()
|
||||
|
|
@ -44,6 +63,13 @@ pub fn spawn(command: &Command) -> Result<(), Error> {
|
|||
}
|
||||
|
||||
pub fn read(command: &Command) -> Result<String, Error> {
|
||||
if command.escalated() {
|
||||
return Err(Error {
|
||||
message: "Read function is strictly rootless".to_string(),
|
||||
kind: ErrorKind::RootlessReadOnly,
|
||||
});
|
||||
}
|
||||
|
||||
if let Ok(output) = process::Command::new(&command.base)
|
||||
.args(&command.args)
|
||||
.output()
|
||||
|
|
@ -68,15 +94,29 @@ pub fn read(command: &Command) -> Result<String, Error> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub fn print(message: &str) -> Result<(), Error> {
|
||||
println!("{message}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn print_packages(packages: Vec<Package>) -> Result<(), Error> {
|
||||
for package in packages {
|
||||
print(&format!("{package}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
pub kind: ErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
CommandNotFound,
|
||||
RootlessReadOnly,
|
||||
#[default]
|
||||
Unknown,
|
||||
FailedSpawn,
|
||||
ChildExit,
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@ pub fn fulfill(state: &State) -> Result<(), Error> {
|
|||
TaskKind::Help => executor::print("<long help>")?,
|
||||
TaskKind::PackageInstall => {
|
||||
let packages: Vec<Package> = task.parameters.iter().map(|s| s.into()).collect();
|
||||
state
|
||||
let mut transaction = state
|
||||
.os()
|
||||
.packager()
|
||||
.install(&packages, state.configuration())?;
|
||||
executor::commit(&mut transaction)?;
|
||||
}
|
||||
TaskKind::PackageUninstall => {
|
||||
let packages: Vec<Package> = task.parameters.iter().map(|s| s.into()).collect();
|
||||
state
|
||||
let mut transaction = state
|
||||
.os()
|
||||
.packager()
|
||||
.uninstall(&packages, state.configuration())?;
|
||||
executor::commit(&mut transaction)?;
|
||||
}
|
||||
TaskKind::PackageListAuto => {
|
||||
match state.os().packager().automatic() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue