WIP: Generic activation program #9

Draft
ecoppens wants to merge 11 commits from ecoppens/colmena:main into main
7 changed files with 58 additions and 9 deletions
Showing only changes of commit 596a40020b - Show all commits

View file

@ -28,7 +28,7 @@ use super::{
host::Local as LocalHost, host::Local as LocalHost,
key::{Key, UploadAt as UploadKeyAt}, key::{Key, UploadAt as UploadKeyAt},
ColmenaError, ColmenaResult, CopyDirection, CopyOptions, Hive, Host, NodeConfig, NodeName, ColmenaError, ColmenaResult, CopyDirection, CopyOptions, Hive, Host, NodeConfig, NodeName,
Profile, ProfileDerivation, RebootOptions, Profile, ProfileDerivation, RebootOptions, StorePath,
}; };
/// A deployment. /// A deployment.
@ -637,7 +637,18 @@ impl Deployment {
} }
} }
host.activate(&profile_r, arc_self.goal).await?; let activation_program: &StorePath =
&arc_self.hive
.get_registry_config()
.await?
.systems
.get(target.config.system_type.as_ref().unwrap_or(&"nixos".to_owned()))
.expect(&format!("System type {} does not exists",
target.config.system_type.as_ref().unwrap_or(&"nixos".to_owned())
))
.activation_program;
host.activate(&profile_r, arc_self.goal, activation_program).await?;
job.success_with_message(arc_self.goal.success_str().to_string())?; job.success_with_message(arc_self.goal.success_str().to_string())?;

View file

@ -258,6 +258,12 @@ with builtins; rec {
type = types.functionTo types.unspecified; type = types.functionTo types.unspecified;
default = _: {}; default = _: {};
}; };
activationProgram = lib.mkOption {
description = mdDoc ''
Program to execute at activation time.
'';
type = types.path;
};
}; };
}; };
registryOptions = { lib, ... }: let registryOptions = { lib, ... }: let

View file

@ -78,7 +78,12 @@ impl Host for Local {
Ok(()) Ok(())
} }
async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> { async fn activate(
&mut self,
profile: &Profile,
goal: Goal,
activation_program: &StorePath,
) -> ColmenaResult<()> {
if !goal.requires_activation() { if !goal.requires_activation() {
return Err(ColmenaError::Unsupported); return Err(ColmenaError::Unsupported);
} }
@ -91,7 +96,9 @@ impl Host for Local {
} }
let command = { let command = {
let activation_command = profile.activation_command(goal).unwrap(); let activation_command = profile
.activation_command(goal, activation_program)
.unwrap();
self.make_privileged_command(&activation_command) self.make_privileged_command(&activation_command)
}; };

View file

@ -128,6 +128,7 @@ pub trait Host: Send + Sync + std::fmt::Debug {
profile: &Profile, profile: &Profile,
goal: Goal, goal: Goal,
copy_options: CopyOptions, copy_options: CopyOptions,
activation_program: Option<&StorePath>,
) -> ColmenaResult<()> { ) -> ColmenaResult<()> {
self.copy_closure( self.copy_closure(
profile.as_store_path(), profile.as_store_path(),
@ -137,7 +138,12 @@ pub trait Host: Send + Sync + std::fmt::Debug {
.await?; .await?;
if goal.requires_activation() { if goal.requires_activation() {
self.activate(profile, goal).await?; self.activate(
profile,
goal,
activation_program.expect("Unknown activation program"),
)
.await?;
} }
Ok(()) Ok(())
@ -171,7 +177,12 @@ pub trait Host: Send + Sync + std::fmt::Debug {
/// ///
/// The profile must already exist on the host. You should probably use deploy instead. /// The profile must already exist on the host. You should probably use deploy instead.
#[allow(unused_variables)] #[allow(unused_variables)]
async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> { async fn activate(
&mut self,
profile: &Profile,
goal: Goal,
activation_program: &StorePath,
) -> ColmenaResult<()> {
Err(ColmenaError::Unsupported) Err(ColmenaError::Unsupported)
} }

View file

@ -90,7 +90,12 @@ impl Host for Ssh {
Ok(()) Ok(())
} }
async fn activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()> { async fn activate(
&mut self,
profile: &Profile,
goal: Goal,
activation_program: &StorePath,
) -> ColmenaResult<()> {
if !goal.requires_activation() { if !goal.requires_activation() {
return Err(ColmenaError::Unsupported); return Err(ColmenaError::Unsupported);
} }
@ -101,7 +106,9 @@ impl Host for Ssh {
self.run_command(set_profile).await?; self.run_command(set_profile).await?;
} }
let activation_command = profile.activation_command(goal).unwrap(); let activation_command = profile
.activation_command(goal, activation_program)
.unwrap();
let v: Vec<&str> = activation_command.iter().map(|s| &**s).collect(); let v: Vec<&str> = activation_command.iter().map(|s| &**s).collect();
let command = self.ssh(&v); let command = self.ssh(&v);
self.run_command(command).await self.run_command(command).await

View file

@ -101,6 +101,9 @@ pub struct MetaConfig {
pub struct SystemTypeConfig { pub struct SystemTypeConfig {
#[serde(rename = "supportsDeployment")] #[serde(rename = "supportsDeployment")]
pub supports_deployment: bool, pub supports_deployment: bool,
#[serde(rename = "activationProgram")]
pub activation_program: StorePath,
} }
#[derive(Debug, Clone, Validate, Deserialize)] #[derive(Debug, Clone, Validate, Deserialize)]

View file

@ -26,7 +26,11 @@ impl Profile {
} }
/// Returns the command to activate this profile. /// Returns the command to activate this profile.
pub fn activation_command(&self, goal: Goal) -> Option<Vec<String>> { pub fn activation_command(
&self,
goal: Goal,
activation_program: &StorePath,
) -> Option<Vec<String>> {
if let Some(goal) = goal.as_str() { if let Some(goal) = goal.as_str() {
let path = self.as_path().join("bin/switch-to-configuration"); let path = self.as_path().join("bin/switch-to-configuration");
let switch_to_configuration = path let switch_to_configuration = path