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.
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]]
name = "autocfg"
version = "1.1.0"
@ -63,6 +78,19 @@ dependencies = [
"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]]
name = "errno"
version = "0.2.8"
@ -99,6 +127,12 @@ dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "io-lifetimes"
version = "1.0.4"
@ -139,6 +173,21 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "memoffset"
version = "0.7.1"
@ -222,6 +271,23 @@ dependencies = [
"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]]
name = "rustix"
version = "0.36.7"
@ -277,7 +343,10 @@ dependencies = [
name = "service-manager"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"log",
"nix",
"serde",
"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
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.1.4", features = ["derive"] }
env_logger = "0.10.0"
log = "0.4.17"
nix = "0.26.2"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Result};
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use std::os::unix;
use std::path::Path;
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();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
match args.action {
Action::Activate { store_path } => activate(store_path),
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()) {
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 reader = io::BufReader::new(file);
let services: Vec<ServiceConfig> = serde_json::from_reader(reader)?;
println!("{:?}", services);
services.iter().try_for_each(|service| {
create_store_link(
@ -102,7 +104,7 @@ fn start_services(services: &[ServiceConfig]) {
.success()
{
services.iter().for_each(|service| {
println!("Starting service {} ...", service.name);
log::info!("Starting service {} ...", service.name);
let output = print_out_and_err(
process::Command::new("systemctl")
.arg("start")
@ -111,15 +113,15 @@ fn start_services(services: &[ServiceConfig]) {
.expect("Unable to run systemctl"),
);
if output.status.success() {
println!("Started service {}", service.name);
log::info!("Started service {}", service.name);
} 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")?;
// TODO: we temporarily put this under per-user to avoid needing root access
// 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
let flake_attr = format!("{}.x86_64-linux", FLAKE_ATTR);
println!("Running nix build...");
let nix_build_output = run_nix_build(flake_uri, &flake_attr);
let store_path = get_store_path(nix_build_output)?;
log::info!("Running nix build...");
let store_path = run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)?;
println!("Generating new generation from {}", store_path);
print_out_and_err(install_nix_profile(&store_path, &profile_path));
log::info!("Generating new generation from {}", store_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)?;
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")
.arg("--profile")
.arg(profile_path)
.arg("--set")
.arg(&store_path.path)
.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 store_path = StorePath::from(String::from(profile_store_path.to_string_lossy()));
create_store_link(&store_path, Path::new(gcroot_path))
}
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<(), Box<dyn Error>> {
println!("Creating symlink: {} -> {}", from.display(), store_path);
fn create_store_link(store_path: &StorePath, from: &Path) -> Result<()> {
log::info!("Creating symlink: {} -> {}", from.display(), store_path);
if from.is_symlink() {
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() {
String::from_utf8(nix_build_result.stdout)
.map_err(Box::from)
.map_err(anyhow::Error::from)
.and_then(parse_nix_build_output)
} 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>,
}
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 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) {
return Ok(StorePath::from(store_path.to_owned()));
}
return Err(format!(
return Err(anyhow!(
"No output '{}' found in nix build result.",
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")
.arg("build")
.arg(format!("{}#{}", flake_uri, flake_attr))
.arg("--json")
.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 {
@ -219,7 +226,7 @@ fn print_out_and_err(output: process::Output) -> process::Output {
fn print_u8(bytes: &[u8]) {
str::from_utf8(bytes).map_or((), |s| {
if !s.trim().is_empty() {
println!("{}", s.trim())
log::info!("{}", s.trim())
}
})
}
@ -231,10 +238,3 @@ where
{
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)
}