Improve error handling and logging.
This commit is contained in:
parent
10b7ba5954
commit
4592e2bacc
3 changed files with 112 additions and 40 deletions
69
Cargo.lock
generated
69
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
80
src/main.rs
80
src/main.rs
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue