From f3ea6ee9d5b8c4ca6b9f2b8b756b2c5cef8acae4 Mon Sep 17 00:00:00 2001
From: clerie <git@clerie.de>
Date: Sat, 25 Mar 2023 13:42:58 +0100
Subject: [PATCH] Refactor, add NixStorePath class

---
 Cargo.lock  |  2 +-
 Cargo.toml  |  2 +-
 flake.nix   |  2 +-
 src/main.rs | 88 ++++++++++++++++++++++++++++++++++-------------------
 4 files changed, 59 insertions(+), 35 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f7d435f..5fdc77b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -450,7 +450,7 @@ dependencies = [
 
 [[package]]
 name = "nixos-exporter"
-version = "0.3.0"
+version = "0.4.0"
 dependencies = [
  "axum",
  "reqwest",
diff --git a/Cargo.toml b/Cargo.toml
index 9dd0125..f8fa6d8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nixos-exporter"
-version = "0.3.0"
+version = "0.4.0"
 edition = "2021"
 
 [dependencies]
diff --git a/flake.nix b/flake.nix
index 6dd91c1..a8eab61 100644
--- a/flake.nix
+++ b/flake.nix
@@ -10,7 +10,7 @@
     in {
       nixos-exporter = pkgs.rustPlatform.buildRustPackage rec {
         pname = "nixos-exporter";
-        version = "0.3.0";
+        version = "0.4.0";
 
         src = ./.;
 
diff --git a/src/main.rs b/src/main.rs
index 6d675e3..e1cae60 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ use axum::{
 use std::collections::HashMap;
 use std::net::SocketAddr;
 use std::str::FromStr;
+use std::path::PathBuf;
 
 #[derive(Clone, PartialEq)]
 enum OperationMode {
@@ -58,16 +59,44 @@ impl AppState{
     }
 }
 
-fn parse_nix_store_path(path: std::path::PathBuf) -> Result<(String, String), String> {
-    let (hash, name) = path.iter().nth(3)
-        .ok_or_else(|| String::from("Can't read store path name"))?
-        .to_str()
-        .ok_or_else(|| String::from("Failed converting store path name to string"))?
-        .split_once("-")
-        .ok_or_else(|| String::from("Failed splitting store path name for hash and name"))?;
-    return Ok((hash.to_string(), name.to_string()));
+#[derive(Clone)]
+struct NixStorePath {
+    hash: String,
+    name: String,
 }
 
+impl NixStorePath {
+    pub fn from_str_symlink(path: &str) -> Result<Self, String> {
+        Ok(Self::from_path_buf_symlink(PathBuf::from(path))?)
+    }
+
+    pub fn from_path_buf_symlink(path: PathBuf) -> Result<Self, String> {
+        Ok(Self::from_path_buf(path.read_link().map_err(|err| err.to_string())?)?)
+    }
+
+    pub fn from_path_buf(path: PathBuf) -> Result<Self, String> {
+        let store_path_name = path.iter().nth(3)
+            .ok_or_else(|| String::from("Can't read store path name"))?
+            .to_str()
+            .ok_or_else(|| String::from("Failed converting store path name to string"))?
+            .to_string();
+        Ok(Self::from_store_path_name(store_path_name)?)
+    }
+
+    pub fn from_store_path_name(store_path_name: String) -> Result<Self, String> {
+        let (hash, name) = store_path_name
+            .split_once("-")
+            .ok_or_else(|| String::from("Failed splitting store path name for hash and name"))?;
+        Ok(Self {
+            hash: hash.to_string(),
+            name: name.to_string(),
+        })
+    }
+
+    pub fn to_prometheus_metric(self, infix: String) -> Result<String, String> {
+        return Ok(format!("nixos_{}_hash{{hash=\"{}\"}} 1\nnixos_{}_name{{name=\"{}\"}} 1\n", infix, self.hash, infix, self.name));
+    }
+}
 
 #[tokio::main]
 async fn main() {
@@ -143,30 +172,24 @@ async fn main() {
         .unwrap();
 }
 
-fn parse_symlink(path: String) -> Result<(String, String), String> {
-    let symlink = std::fs::read_link(path).map_err(|err| err.to_string())?;
-
-    let (hash, name) = parse_nix_store_path(symlink)?;
-
-    Ok((String::from(hash), String::from(name)))
-}
-
-fn gen_prometheus_metric(path: String, infix: String) -> Result<String, String> {
-    let (hash, name) = parse_symlink(path).map_err(|err| err)?;
-
-    return Ok(format!("nixos_{}_hash{{hash=\"{}\"}} 1\nnixos_{}_name{{name=\"{}\"}} 1\n", infix, hash, infix, name));
-}
-
 async fn metrics() -> Result<(StatusCode, impl IntoResponse), (StatusCode, impl IntoResponse)> {
+    let nix_store_paths = HashMap::from([
+    ("current_system", NixStorePath::from_str_symlink("/run/current-system")
+        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?),
+    ("current_system_kernel", NixStorePath::from_str_symlink("/run/current-system/kernel")
+        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?),
+    ("booted_system", NixStorePath::from_str_symlink("/run/booted-system")
+        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?),
+    ("booted_system_kernel", NixStorePath::from_str_symlink("/run/booted-system/kernel")
+        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?),
+    ]);
+
     let mut out = String::new();
-    out.push_str(&gen_prometheus_metric(String::from("/run/current-system"), String::from("current_system"))
-        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?);
-    out.push_str(&gen_prometheus_metric(String::from("/run/current-system/kernel"), String::from("current_system_kernel"))
-        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?);
-    out.push_str(&gen_prometheus_metric(String::from("/run/booted-system"), String::from("booted_system"))
-        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?);
-    out.push_str(&gen_prometheus_metric(String::from("/run/booted-system/kernel"), String::from("booted_system_kernel"))
-        .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?);
+    for (infix, nix_store_path) in nix_store_paths.iter() {
+        out.push_str(nix_store_path.clone().to_prometheus_metric(infix.to_string())
+            .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?.as_str());
+    }
+
     return Ok((
         StatusCode::OK,
         out,
@@ -213,8 +236,9 @@ async fn check(State(app_state): State<AppState>, Query(params): Query<HashMap<S
     let nix_store_path = hydra_body["buildoutputs"]["out"]["path"].as_str()
         .ok_or_else(|| (StatusCode::INTERNAL_SERVER_ERROR, "No buildoutput found in Hydra"))?;
 
-    let (hydra_system_hash, _) = parse_nix_store_path(std::path::PathBuf::from(nix_store_path))
-        .map_err(|_err| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid store path returned by Hydra"))?;
+    let hydra_system_hash = NixStorePath::from_path_buf(std::path::PathBuf::from(nix_store_path))
+        .map_err(|_err| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid store path returned by Hydra"))?
+        .hash;
 
     let mut status = "0";