Support different planners

This commit is contained in:
Ana Hobden 2022-10-14 15:14:03 -07:00
parent 33879821c3
commit 144af153f6
13 changed files with 210 additions and 27 deletions

View file

@ -1,4 +1,5 @@
use clap::{ArgAction, Parser};
use harmonic::Planner;
/// Plan an install that can be repeated on an identical host later
#[derive(Debug, Parser)]
@ -38,4 +39,7 @@ pub(crate) struct PlanOptions {
group = "plan_options"
)]
pub(crate) force: bool,
// Override the default planner for this OS/Architecture
#[clap(long, global = true, group = "plan_options", value_parser = clap::builder::EnumValueParser::<Planner>::new())]
pub(crate) planner: Option<Planner>,
}

View file

@ -2,7 +2,7 @@ use std::{path::PathBuf, process::ExitCode};
use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr};
use harmonic::{InstallPlan, InstallSettings};
use harmonic::{InstallPlan, InstallSettings, Planner};
use crate::{
cli::{
@ -69,7 +69,12 @@ impl CommandExecute for Install {
);
settings.modify_profile(!plan_options.no_modify_profile);
InstallPlan::new(settings).await?
let planner = match plan_options.planner {
Some(planner) => planner,
None => Planner::default()?,
};
InstallPlan::new(planner, settings).await?
},
};

View file

@ -1,7 +1,7 @@
use std::{path::PathBuf, process::ExitCode};
use clap::Parser;
use harmonic::{InstallPlan, InstallSettings};
use harmonic::{InstallPlan, InstallSettings, Planner};
use eyre::WrapErr;
@ -34,6 +34,7 @@ impl CommandExecute for Plan {
no_modify_profile,
daemon_user_count,
force,
planner,
},
plan,
} = self;
@ -49,7 +50,12 @@ impl CommandExecute for Plan {
);
settings.modify_profile(!no_modify_profile);
let install_plan = InstallPlan::new(settings).await?;
let planner = match planner {
Some(planner) => planner,
None => Planner::default()?,
};
let install_plan = InstallPlan::new(planner, settings).await?;
let json = serde_json::to_string_pretty(&install_plan)?;
tokio::fs::write(plan, json)

View file

@ -1,12 +1,14 @@
mod actions;
mod error;
mod plan;
mod planner;
mod settings;
use std::{ffi::OsStr, fmt::Display, process::ExitStatus};
pub use error::HarmonicError;
pub use plan::InstallPlan;
pub use planner::Planner;
use serde::Serializer;
pub use settings::InstallSettings;

View file

@ -5,41 +5,32 @@ use crate::{
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
Action, ActionDescription, ActionError, Actionable,
},
planner::PlannerError,
settings::InstallSettings,
HarmonicError,
HarmonicError, Planner,
};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct InstallPlan {
settings: InstallSettings,
pub(crate) settings: InstallSettings,
actions: Vec<Action>,
pub(crate) actions: Vec<Action>,
pub(crate) planner: Planner,
}
impl InstallPlan {
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
Ok(Self {
settings: settings.clone(),
actions: vec![
ProvisionNix::plan(settings.clone())
.await
.map(Action::from)
.map_err(ActionError::from)?,
ConfigureNix::plan(settings)
.await
.map(Action::from)
.map_err(ActionError::from)?,
StartNixDaemon::plan()
.await
.map(Action::from)
.map_err(ActionError::from)?,
],
})
pub async fn new(planner: Planner, settings: InstallSettings) -> Result<Self, PlannerError> {
planner.plan(settings).await
}
#[tracing::instrument(skip_all)]
pub fn describe_execute(&self, explain: bool) -> String {
let Self { settings, actions } = self;
let Self {
planner,
settings,
actions,
} = self;
format!(
"\
This Nix install is for:\n\
@ -47,6 +38,8 @@ impl InstallPlan {
Init system: {init_type}\n\
Nix channels: {nix_channels}\n\
\n\
Created by planner: {planner:?}
\n\
The following actions will be taken:\n\
{actions}
",
@ -87,6 +80,7 @@ impl InstallPlan {
let Self {
actions,
settings: _,
planner: _,
} = self;
// This is **deliberately sequential**.
@ -104,7 +98,11 @@ impl InstallPlan {
#[tracing::instrument(skip_all)]
pub fn describe_revert(&self, explain: bool) -> String {
let Self { settings, actions } = self;
let Self {
planner,
settings,
actions,
} = self;
format!(
"\
This Nix uninstall is for:\n\
@ -112,6 +110,8 @@ impl InstallPlan {
Init system: {init_type}\n\
Nix channels: {nix_channels}\n\
\n\
Created by planner: {planner:?}
\n\
The following actions will be taken:\n\
{actions}
",
@ -152,6 +152,7 @@ impl InstallPlan {
let Self {
actions,
settings: _,
planner: _,
} = self;
// This is **deliberately sequential**.

View file

@ -0,0 +1,3 @@
mod multi_user;
pub use multi_user::DarwinMultiUser;

View file

@ -0,0 +1,22 @@
use crate::{planner::Plannable, Planner};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct DarwinMultiUser;
#[async_trait::async_trait]
impl Plannable for DarwinMultiUser {
const DISPLAY_STRING: &'static str = "Darwin Multi-User";
const SLUG: &'static str = "darwin-multi";
async fn plan(
settings: crate::InstallSettings,
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
todo!()
}
}
impl Into<Planner> for DarwinMultiUser {
fn into(self) -> Planner {
Planner::DarwinMultiUser
}
}

3
src/planner/linux/mod.rs Normal file
View file

@ -0,0 +1,3 @@
mod multi_user;
pub use multi_user::LinuxMultiUser;

View file

@ -0,0 +1,44 @@
use crate::{
actions::{
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
Action, ActionError,
},
planner::{Plannable, PlannerError},
InstallPlan, InstallSettings, Planner,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct LinuxMultiUser;
#[async_trait::async_trait]
impl Plannable for LinuxMultiUser {
const DISPLAY_STRING: &'static str = "Linux Multi-User";
const SLUG: &'static str = "linux-multi";
async fn plan(settings: InstallSettings) -> Result<InstallPlan, PlannerError> {
Ok(InstallPlan {
planner: Self.into(),
settings: settings.clone(),
actions: vec![
ProvisionNix::plan(settings.clone())
.await
.map(Action::from)
.map_err(ActionError::from)?,
ConfigureNix::plan(settings)
.await
.map(Action::from)
.map_err(ActionError::from)?,
StartNixDaemon::plan()
.await
.map(Action::from)
.map_err(ActionError::from)?,
],
})
}
}
impl Into<Planner> for LinuxMultiUser {
fn into(self) -> Planner {
Planner::LinuxMultiUser
}
}

61
src/planner/mod.rs Normal file
View file

@ -0,0 +1,61 @@
mod darwin;
mod linux;
mod specific;
use std::{ffi::OsStr, str::FromStr};
use crate::{actions::ActionError, HarmonicError, InstallPlan, InstallSettings};
#[derive(Debug, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
pub enum Planner {
LinuxMultiUser,
DarwinMultiUser,
SteamDeck,
}
impl Planner {
pub fn possible_values() -> &'static [Planner] {
&[Self::LinuxMultiUser, Self::DarwinMultiUser, Self::SteamDeck]
}
pub fn default() -> Result<Self, PlannerError> {
use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) {
(Architecture::X86_64, OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
(Architecture::Aarch64(_), OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
(Architecture::X86_64, OperatingSystem::MacOSX { .. }) => Ok(Self::DarwinMultiUser),
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) => Ok(Self::DarwinMultiUser),
_ => Err(PlannerError::UnsupportedArchitecture(target_lexicon::HOST)),
}
}
pub async fn plan(self, settings: InstallSettings) -> Result<InstallPlan, PlannerError> {
match self {
Planner::LinuxMultiUser => linux::LinuxMultiUser::plan(settings).await,
Planner::DarwinMultiUser => darwin::DarwinMultiUser::plan(settings).await,
Planner::SteamDeck => specific::SteamDeck::plan(settings).await,
}
}
}
#[async_trait::async_trait]
trait Plannable: Into<Planner>
where
Self: Sized,
{
const DISPLAY_STRING: &'static str;
const SLUG: &'static str;
async fn plan(settings: InstallSettings) -> Result<InstallPlan, PlannerError>;
}
#[derive(thiserror::Error, Debug)]
pub enum PlannerError {
#[error("Harmonic does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
UnsupportedArchitecture(target_lexicon::Triple),
#[error("Error executing action")]
ActionError(
#[source]
#[from]
ActionError,
),
}

View file

@ -0,0 +1,3 @@
mod steam_deck;
pub use steam_deck::SteamDeck;

View file

@ -0,0 +1,22 @@
use crate::{planner::Plannable, Planner};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct SteamDeck;
#[async_trait::async_trait]
impl Plannable for SteamDeck {
const DISPLAY_STRING: &'static str = "Steam Deck (x86_64 Linux Multi-User)";
const SLUG: &'static str = "steam-deck";
async fn plan(
settings: crate::InstallSettings,
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
todo!()
}
}
impl Into<Planner> for SteamDeck {
fn into(self) -> Planner {
Planner::SteamDeck
}
}

View file

@ -1,3 +1,4 @@
use crate::{planner, Planner};
use target_lexicon::Triple;
use url::Url;
@ -115,4 +116,10 @@ impl InstallSettings {
pub enum InstallSettingsError {
#[error("Harmonic does not support the `{0}` architecture right now")]
UnsupportedArchitecture(target_lexicon::Triple),
#[error("Planner error")]
Planner(
#[source]
#[from]
planner::PlannerError,
),
}