Fix handling of the --store argument. (#12)

This commit is contained in:
Ramses 2023-05-12 16:04:24 +02:00 committed by GitHub
parent 39bcc3643c
commit f4c58ce90b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 38 deletions

View file

@ -53,6 +53,11 @@
inherit nixpkgs self; inherit nixpkgs self;
nixos = "${nixpkgs}/nixos"; nixos = "${nixpkgs}/nixos";
}; };
# Only useful for quick tests
systemConfigs.default = self.lib.makeSystemConfig {
modules = [ ./examples/example.nix ];
};
} }
// //
(flake-utils.lib.eachSystem (flake-utils.lib.eachSystem

View file

@ -22,15 +22,12 @@ pub struct StorePath {
pub store_path: PathBuf, pub store_path: PathBuf,
} }
// We cannot implement both From and TryFrom, and we need From for Deserialize...
impl From<String> for StorePath { impl From<String> for StorePath {
fn from(path: String) -> Self { fn from(path: String) -> Self {
// TODO: handle this better PathBuf::from(path)
if !path.starts_with("/nix/store/") { .try_into()
panic!("Error constructing store path, not in store: {path}"); .expect("Error constructing store path, path not in store.")
}
StorePath {
store_path: PathBuf::from(path),
}
} }
} }
@ -40,6 +37,22 @@ impl From<StorePath> for PathBuf {
} }
} }
impl TryFrom<PathBuf> for StorePath {
type Error = anyhow::Error;
fn try_from(path: PathBuf) -> Result<Self> {
let canon = path.canonicalize()?;
if !canon.starts_with(PathBuf::from("/").join("nix").join("store")) {
anyhow::bail!(
"Error constructing store path, not in store: {} (canonicalised: {})",
path.display(),
canon.display()
);
}
Ok(Self { store_path: canon })
}
}
impl From<StorePath> for String { impl From<StorePath> for String {
fn from(value: StorePath) -> Self { fn from(value: StorePath) -> Self {
format!("{}", value.store_path.display()) format!("{}", value.store_path.display())

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process::{self, ExitCode}; use std::process::{self, ExitCode};
use system_manager::StorePath; use system_manager::StorePath;
@ -53,12 +53,12 @@ struct ActivationArgs {
} }
#[derive(clap::Args, Debug)] #[derive(clap::Args, Debug)]
struct DeactivationArgs { struct OptionalStorePathArgs {
#[arg(long)] #[arg(long = "store-path", name = "STORE_PATH")]
/// The store path for the system-manager profile. /// The store path for the system-manager profile.
/// You only need to specify this explicitly if it differs from the active /// You only need to specify this explicitly if it differs from the active
/// system-manager profile /// system-manager profile.
store_path: Option<StorePath>, maybe_store_path: Option<StorePath>,
} }
#[derive(clap::Subcommand, Debug)] #[derive(clap::Subcommand, Debug)]
@ -71,10 +71,11 @@ enum Action {
#[command(flatten)] #[command(flatten)]
activation_args: ActivationArgs, activation_args: ActivationArgs,
}, },
/// Put all files defined by the given generation in place, but do not start
/// services. Useful in build scripts.
PrePopulate { PrePopulate {
#[arg(long)] #[command(flatten)]
/// The store path containing the system-manager profile to activate optional_store_path_args: OptionalStorePathArgs,
store_path: StorePath,
#[command(flatten)] #[command(flatten)]
activation_args: ActivationArgs, activation_args: ActivationArgs,
}, },
@ -86,7 +87,7 @@ enum Action {
/// Deactivate the active system-manager profile, removing all managed configuration /// Deactivate the active system-manager profile, removing all managed configuration
Deactivate { Deactivate {
#[command(flatten)] #[command(flatten)]
deactivation_args: DeactivationArgs, optional_store_path_args: OptionalStorePathArgs,
}, },
/// Generate a new system-manager profile and /// Generate a new system-manager profile and
/// register is as the active system-manager profile /// register is as the active system-manager profile
@ -125,9 +126,10 @@ fn go(args: Args) -> Result<()> {
activate(&store_path, ephemeral, &target_host, use_remote_sudo) activate(&store_path, ephemeral, &target_host, use_remote_sudo)
} }
Action::PrePopulate { Action::PrePopulate {
store_path, optional_store_path_args: OptionalStorePathArgs { maybe_store_path },
activation_args: ActivationArgs { ephemeral }, activation_args: ActivationArgs { ephemeral },
} => { } => {
let store_path = store_path_or_active_profile(maybe_store_path).try_into()?;
copy_closure(&store_path, &target_host)?; copy_closure(&store_path, &target_host)?;
prepopulate(&store_path, ephemeral, &target_host, use_remote_sudo) prepopulate(&store_path, ephemeral, &target_host, use_remote_sudo)
} }
@ -135,8 +137,8 @@ fn go(args: Args) -> Result<()> {
build_args: BuildArgs { flake_uri }, build_args: BuildArgs { flake_uri },
} => build(&flake_uri, &target_host), } => build(&flake_uri, &target_host),
Action::Deactivate { Action::Deactivate {
deactivation_args: DeactivationArgs { store_path }, optional_store_path_args: OptionalStorePathArgs { maybe_store_path },
} => deactivate(store_path, &target_host, use_remote_sudo), } => deactivate(maybe_store_path, &target_host, use_remote_sudo),
Action::Generate { generate_args } => { Action::Generate { generate_args } => {
generate(&generate_args, &target_host, use_remote_sudo) generate(&generate_args, &target_host, use_remote_sudo)
} }
@ -260,20 +262,13 @@ fn prepopulate(
} }
fn deactivate( fn deactivate(
store_path: Option<StorePath>, maybe_store_path: Option<StorePath>,
target_host: &Option<String>, target_host: &Option<String>,
use_remote_sudo: bool, use_remote_sudo: bool,
) -> Result<()> { ) -> Result<()> {
if let Some(target_host) = target_host { if let Some(target_host) = target_host {
invoke_remote_script( let store_path = store_path_or_active_profile(maybe_store_path);
&store_path.map_or_else( invoke_remote_script(&store_path, "deactivate", target_host, use_remote_sudo)?;
|| Path::new(system_manager::PROFILE_DIR).join("system-manager"),
|store_path| store_path.store_path,
),
"deactivate",
target_host,
use_remote_sudo,
)?;
Ok(()) Ok(())
} else { } else {
check_root()?; check_root()?;
@ -306,7 +301,7 @@ fn do_copy_closure(store_path: &StorePath, target_host: &str) -> Result<()> {
} }
fn invoke_remote_script( fn invoke_remote_script(
store_path: &Path, path: &Path,
script_name: &str, script_name: &str,
target_host: &str, target_host: &str,
use_remote_sudo: bool, use_remote_sudo: bool,
@ -318,8 +313,7 @@ fn invoke_remote_script(
} }
let status = cmd let status = cmd
.arg(OsString::from( .arg(OsString::from(
store_path path.join("bin")
.join("bin")
.join(script_name) .join(script_name)
.to_string_lossy() .to_string_lossy()
.into_owned(), .into_owned(),
@ -337,6 +331,17 @@ fn check_root() -> Result<()> {
Ok(()) Ok(())
} }
fn store_path_or_active_profile(maybe_store_path: Option<StorePath>) -> PathBuf {
maybe_store_path.map_or_else(
|| {
let path = Path::new(system_manager::PROFILE_DIR).join("system-manager");
log::info!("No store path provided, using {}", path.display());
path
},
|store_path| store_path.store_path,
)
}
fn handle_toplevel_error<T>(r: Result<T>) -> ExitCode { fn handle_toplevel_error<T>(r: Result<T>) -> ExitCode {
if let Err(e) = r { if let Err(e) = r {
log::error!("{:?}", e); log::error!("{:?}", e);

View file

@ -71,7 +71,7 @@ forEachUbuntuImage
node1.wait_for_unit("default.target") node1.wait_for_unit("default.target")
node1.execute("/system-manager-profile/bin/activate") node1.succeed("/system-manager-profile/bin/activate")
node1.wait_for_unit("system-manager.target") node1.wait_for_unit("system-manager.target")
node1.wait_for_unit("service-9.service") node1.wait_for_unit("service-9.service")
@ -81,7 +81,7 @@ forEachUbuntuImage
node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf") node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf") node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
node1.execute("${newConfig}/bin/activate") node1.succeed("${newConfig}/bin/activate")
node1.wait_for_unit("new-service.service") node1.wait_for_unit("new-service.service")
node1.wait_until_fails("systemctl status service-9.service") node1.wait_until_fails("systemctl status service-9.service")
node1.wait_until_fails("cat /etc/a/nested/example/foo3") node1.wait_until_fails("cat /etc/a/nested/example/foo3")
@ -104,7 +104,7 @@ forEachUbuntuImage
node1.wait_until_fails("cat /etc/baz/bar/foo2") node1.wait_until_fails("cat /etc/baz/bar/foo2")
node1.wait_for_file("/etc/foo_new") node1.wait_for_file("/etc/foo_new")
node1.execute("${newConfig}/bin/deactivate") node1.succeed("${newConfig}/bin/deactivate")
node1.wait_until_fails("systemctl status new-service.service") node1.wait_until_fails("systemctl status new-service.service")
node1.wait_until_fails("cat /etc/foo_new") node1.wait_until_fails("cat /etc/foo_new")
''; '';
@ -145,7 +145,7 @@ forEachUbuntuImage
node1.wait_for_unit("default.target") node1.wait_for_unit("default.target")
node1.execute("/system-manager-profile/bin/prepopulate") node1.succeed("/system-manager-profile/bin/prepopulate")
node1.systemctl("daemon-reload") node1.systemctl("daemon-reload")
node1.systemctl("start default.target") node1.systemctl("start default.target")
node1.wait_for_unit("system-manager.target") node1.wait_for_unit("system-manager.target")
@ -157,14 +157,14 @@ forEachUbuntuImage
node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf") node1.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf") node1.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
node1.execute("${newConfig}/bin/activate") node1.succeed("${newConfig}/bin/activate")
node1.wait_for_unit("new-service.service") node1.wait_for_unit("new-service.service")
node1.wait_until_fails("systemctl status service-9.service") node1.wait_until_fails("systemctl status service-9.service")
node1.wait_until_fails("cat /etc/a/nested/example/foo3") node1.wait_until_fails("cat /etc/a/nested/example/foo3")
node1.wait_until_fails("cat /etc/baz/bar/foo2") node1.wait_until_fails("cat /etc/baz/bar/foo2")
node1.wait_for_file("/etc/foo_new") node1.wait_for_file("/etc/foo_new")
node1.execute("${newConfig}/bin/deactivate") node1.succeed("${newConfig}/bin/deactivate")
node1.wait_until_fails("systemctl status new-service.service") node1.wait_until_fails("systemctl status new-service.service")
node1.wait_until_fails("cat /etc/foo_new") node1.wait_until_fails("cat /etc/foo_new")
''; '';