Improve error handling and logging.

This commit is contained in:
R-VdP 2023-02-03 11:17:20 +00:00
parent 10b7ba5954
commit 4592e2bacc
No known key found for this signature in database
3 changed files with 112 additions and 40 deletions

69
Cargo.lock generated
View file

@ -2,6 +2,21 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -63,6 +78,19 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.2.8"
@ -99,6 +127,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.4" version = "1.0.4"
@ -139,6 +173,21 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.7.1" version = "0.7.1"
@ -222,6 +271,23 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.7" version = "0.36.7"
@ -277,7 +343,10 @@ dependencies = [
name = "service-manager" name = "service-manager"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"clap", "clap",
"env_logger",
"log",
"nix", "nix",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -6,7 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.68"
clap = { version = "4.1.4", features = ["derive"] } clap = { version = "4.1.4", features = ["derive"] }
env_logger = "0.10.0"
log = "0.4.17"
nix = "0.26.2" nix = "0.26.2"
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91" serde_json = "1.0.91"

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Result};
use clap::Parser; use clap::Parser;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::os::unix; use std::os::unix;
use std::path::Path; use std::path::Path;
use std::{env, fs, io, process, str}; use std::{env, fs, io, process, str};
@ -47,9 +47,11 @@ enum Action {
}, },
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
match args.action { match args.action {
Action::Activate { store_path } => activate(store_path), Action::Activate { store_path } => activate(store_path),
Action::Generate { flake_uri } => generate(&flake_uri), Action::Generate { flake_uri } => generate(&flake_uri),
@ -69,17 +71,17 @@ impl ServiceConfig {
} }
} }
fn activate(store_path: StorePath) -> Result<(), Box<dyn Error>> { fn activate(store_path: StorePath) -> Result<()> {
if !nix::unistd::Uid::is_root(nix::unistd::getuid()) { if !nix::unistd::Uid::is_root(nix::unistd::getuid()) {
return Err("We need root permissions.".into()); log::error!("We need root permissions");
return Err(anyhow!("We need root permissions."));
} }
println!("Activating service-manager profile: {}", store_path); log::info!("Activating service-manager profile: {}", store_path);
let file = fs::File::open(store_path.path + "/services/services.json")?; let file = fs::File::open(store_path.path + "/services/services.json")?;
let reader = io::BufReader::new(file); let reader = io::BufReader::new(file);
let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?; let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?;
println!("{:?}", services);
services.iter().try_for_each(|service| { services.iter().try_for_each(|service| {
create_store_link( create_store_link(
@ -102,7 +104,7 @@ fn start_services(services: &[ServiceConfig]) {
.success() .success()
{ {
services.iter().for_each(|service| { services.iter().for_each(|service| {
println!("Starting service {} ...", service.name); log::info!("Starting service {} ...", service.name);
let output = print_out_and_err( let output = print_out_and_err(
process::Command::new("systemctl") process::Command::new("systemctl")
.arg("start") .arg("start")
@ -111,15 +113,15 @@ fn start_services(services: &[ServiceConfig]) {
.expect("Unable to run systemctl"), .expect("Unable to run systemctl"),
); );
if output.status.success() { if output.status.success() {
println!("Started service {}", service.name); log::info!("Started service {}", service.name);
} else { } else {
println!("Error starting service {}", service.name); log::error!("Error starting service {}", service.name);
} }
}); });
} }
} }
fn generate(flake_uri: &str) -> Result<(), Box<dyn Error>> { fn generate(flake_uri: &str) -> Result<()> {
let user = env::var("USER")?; let user = env::var("USER")?;
// TODO: we temporarily put this under per-user to avoid needing root access // TODO: we temporarily put this under per-user to avoid needing root access
// we will move this to /nix/var/nix/profiles/ later on. // we will move this to /nix/var/nix/profiles/ later on.
@ -131,49 +133,53 @@ fn generate(flake_uri: &str) -> Result<(), Box<dyn Error>> {
// FIXME: we should not hard-code the system here // FIXME: we should not hard-code the system here
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR); let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
println!("Running nix build..."); log::info!("Running nix build...");
let nix_build_output = run_nix_build(flake_uri, &flake_attr); let store_path = run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)?;
let store_path = get_store_path(nix_build_output)?;
println!("Generating new generation from {}", store_path); log::info!("Generating new generation from {}", store_path);
print_out_and_err(install_nix_profile(&store_path, &profile_path)); install_nix_profile(&store_path, &profile_path).map(print_out_and_err)?;
println!("Registering GC root..."); log::info!("Registering GC root...");
create_gcroot(&gcroot_path, &profile_path)?; create_gcroot(&gcroot_path, &profile_path)?;
Ok(()) Ok(())
} }
fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> process::Output { fn install_nix_profile(store_path: &StorePath, profile_path: &str) -> Result<process::Output> {
process::Command::new("nix-env") process::Command::new("nix-env")
.arg("--profile") .arg("--profile")
.arg(profile_path) .arg(profile_path)
.arg("--set") .arg("--set")
.arg(&store_path.path) .arg(&store_path.path)
.output() .output()
.expect("Failed to execute nix-env, is it on your path?") .map_err(anyhow::Error::from)
} }
fn create_gcroot(gcroot_path: &str, profile_path: &str) -> Result<(), Box<dyn Error>> { fn create_gcroot(gcroot_path: &str, profile_path: &str) -> Result<()> {
let profile_store_path = fs::canonicalize(profile_path)?; let profile_store_path = fs::canonicalize(profile_path)?;
let store_path = StorePath::from(String::from(profile_store_path.to_string_lossy())); let store_path = StorePath::from(String::from(profile_store_path.to_string_lossy()));
create_store_link(&store_path, Path::new(gcroot_path)) create_store_link(&store_path, Path::new(gcroot_path))
} }
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<(), Box<dyn Error>> { fn create_store_link(store_path: &StorePath, from: &Path) -> Result<()> {
println!("Creating symlink: {} -> {}", from.display(), store_path); log::info!("Creating symlink: {} -> {}", from.display(), store_path);
if from.is_symlink() { if from.is_symlink() {
fs::remove_file(from)?; fs::remove_file(from)?;
} }
unix::fs::symlink(&store_path.path, from).map_err(Box::from) unix::fs::symlink(&store_path.path, from).map_err(anyhow::Error::from)
} }
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath, Box<dyn Error>> { fn get_store_path(nix_build_result: process::Output) -> Result<StorePath> {
if nix_build_result.status.success() { if nix_build_result.status.success() {
String::from_utf8(nix_build_result.stdout) String::from_utf8(nix_build_result.stdout)
.map_err(Box::from) .map_err(anyhow::Error::from)
.and_then(parse_nix_build_output) .and_then(parse_nix_build_output)
} else { } else {
String::from_utf8(nix_build_result.stderr).map_or_else(boxed_error(), boxed_error()) String::from_utf8(nix_build_result.stderr)
.map_err(anyhow::Error::from)
.and_then(|e| {
log::error!("{}", e);
Err(anyhow!("Nix build failed."))
})
} }
} }
@ -184,7 +190,7 @@ struct NixBuildOutput {
outputs: HashMap<String, String>, outputs: HashMap<String, String>,
} }
fn parse_nix_build_output(output: String) -> Result<StorePath, Box<dyn Error>> { fn parse_nix_build_output(output: String) -> Result<StorePath> {
let expected_output_name = "out"; let expected_output_name = "out";
let results: Vec<NixBuildOutput> = serde_json::from_str(&output)?; let results: Vec<NixBuildOutput> = serde_json::from_str(&output)?;
@ -192,22 +198,23 @@ fn parse_nix_build_output(output: String) -> Result<StorePath, Box<dyn Error>> {
if let Some(store_path) = result.outputs.get(expected_output_name) { if let Some(store_path) = result.outputs.get(expected_output_name) {
return Ok(StorePath::from(store_path.to_owned())); return Ok(StorePath::from(store_path.to_owned()));
} }
return Err(format!( return Err(anyhow!(
"No output '{}' found in nix build result.", "No output '{}' found in nix build result.",
expected_output_name expected_output_name
) ));
.into());
} }
Err("Multiple build results were returned, we cannot handle that yet.".into()) Err(anyhow!(
"Multiple build results were returned, we cannot handle that yet."
))
} }
fn run_nix_build(flake_uri: &str, flake_attr: &str) -> process::Output { fn run_nix_build(flake_uri: &str, flake_attr: &str) -> Result<process::Output> {
process::Command::new("nix") process::Command::new("nix")
.arg("build") .arg("build")
.arg(format!("{}#{}", flake_uri, flake_attr)) .arg(format!("{}#{}", flake_uri, flake_attr))
.arg("--json") .arg("--json")
.output() .output()
.expect("Failed to execute nix, is it on your path?") .map_err(anyhow::Error::from)
} }
fn print_out_and_err(output: process::Output) -> process::Output { fn print_out_and_err(output: process::Output) -> process::Output {
@ -219,7 +226,7 @@ fn print_out_and_err(output: process::Output) -> process::Output {
fn print_u8(bytes: &[u8]) { fn print_u8(bytes: &[u8]) {
str::from_utf8(bytes).map_or((), |s| { str::from_utf8(bytes).map_or((), |s| {
if !s.trim().is_empty() { if !s.trim().is_empty() {
println!("{}", s.trim()) log::info!("{}", s.trim())
} }
}) })
} }
@ -231,10 +238,3 @@ where
{ {
move |x| f(g(x)) move |x| f(g(x))
} }
fn boxed_error<V, E>() -> impl Fn(E) -> Result<V, Box<dyn Error>>
where
E: Into<Box<dyn Error>>,
{
compose(Err, Into::into)
}