Support different planners
This commit is contained in:
parent
33879821c3
commit
144af153f6
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
47
src/plan.rs
47
src/plan.rs
|
@ -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**.
|
||||
|
|
3
src/planner/darwin/mod.rs
Normal file
3
src/planner/darwin/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod multi_user;
|
||||
|
||||
pub use multi_user::DarwinMultiUser;
|
22
src/planner/darwin/multi_user.rs
Normal file
22
src/planner/darwin/multi_user.rs
Normal 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
3
src/planner/linux/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod multi_user;
|
||||
|
||||
pub use multi_user::LinuxMultiUser;
|
44
src/planner/linux/multi_user.rs
Normal file
44
src/planner/linux/multi_user.rs
Normal 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
61
src/planner/mod.rs
Normal 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,
|
||||
),
|
||||
}
|
3
src/planner/specific/mod.rs
Normal file
3
src/planner/specific/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod steam_deck;
|
||||
|
||||
pub use steam_deck::SteamDeck;
|
22
src/planner/specific/steam_deck.rs
Normal file
22
src/planner/specific/steam_deck.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue