From 25412c01669cf5a820520a1728be13fafa52244a Mon Sep 17 00:00:00 2001 From: Patrick Meade Date: Tue, 18 Jun 2024 18:22:49 -0500 Subject: [PATCH] Created REST service for smartctl access --- .github/workflows/ci.yml | 9 +++ Cargo.lock | 4 +- Cargo.toml | 4 +- src/bin/smartrest.rs | 132 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/bin/smartrest.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0618b2..6fdc9cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,15 @@ env: CARGO_TERM_COLOR: always jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: rustup update stable && rustup default stable + - run: cargo fmt --check + - run: cargo clippy --locked + test: name: Test runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 282809a..04e2caf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,10 +1348,12 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "wipac-disk-tracking" -version = "0.1.0" +version = "0.2.0" dependencies = [ "env_logger", "gotham", "gotham_restful", "log", + "serde", + "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index 29c2894..ddf01c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wipac-disk-tracking" -version = "0.1.0" +version = "0.2.0" edition = "2021" publish = false @@ -9,3 +9,5 @@ env_logger = "0.11.3" gotham = "0.7.4" gotham_restful = "0.9.0" log = "0.4.21" +serde = "1.0.203" +serde_json = "1.0.117" diff --git a/src/bin/smartrest.rs b/src/bin/smartrest.rs new file mode 100644 index 0000000..dd8eabc --- /dev/null +++ b/src/bin/smartrest.rs @@ -0,0 +1,132 @@ +// smartrest.rs + +use gotham::{ + router::{build_simple_router, response::StaticResponseExtender, Router}, + state::StateData, +}; +use gotham_restful::{read_all, search, DrawResources, Resource, Success}; +use log::{error, info}; +use serde::Deserialize; +use serde_json::{json, Value}; +use std::process::Command; + +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +#[derive(Resource)] +#[resource(smartctl_scan)] +#[resource(smartctl_all)] +struct SmartCtlResource; + +#[derive(Clone, Deserialize, StateData, StaticResponseExtender)] +struct DevicePath { + device: String, +} + +#[read_all] +fn smartctl_scan() -> Success { + execute_smartctl_scan().into() +} + +#[search] +fn smartctl_all(path: DevicePath) -> Success { + execute_smartctl_all(&path.device).into() +} + +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +fn execute_smartctl_all(device: &str) -> Value { + // enumerate all the devices on the system + let mut smartctl_devices = Vec::new(); + + // query smartctl to ask what devices we have on the system + let smartctl = execute_smartctl_scan(); + // if we got back some useful data from smartctl + if let Some(smartctl_obj) = smartctl.as_object() { + // if there is a 'devices' array + if let Some(devices) = smartctl_obj.get("devices") { + if let Some(devices_array) = devices.as_array() { + // for each device in the array + for smartctl_device in devices_array { + // if the device has a name + if let Some(device_name) = smartctl_device.get("name") { + if let Some(name) = device_name.as_str() { + // add it to the list of devices + smartctl_devices.push(name); + } + } + } + } + } + } + + // check to see if the provided device is on the list + if smartctl_devices.contains(&device) { + // if we get output from the smartctl command + if let Ok(output) = Command::new("/usr/sbin/smartctl") + .arg("--all") // give us all the information for the device + .arg("--json") // give us the result in JSON + .arg(device) // about this device + .output() + { + // if we can convert that output into a sensible utf8 string + if let Ok(stdout) = String::from_utf8(output.stdout) { + // if we can parse that result into a JSON value + if let Ok(result) = serde_json::from_str(&stdout) { + // give the output to the caller + return result; + } + } + } + } + + // give the caller an empty object + error!( + "Device {} was not found in the list of smartctl devices: {:?}", + device, smartctl_devices + ); + json!("{}") +} + +fn execute_smartctl_scan() -> Value { + // if we get output from the smartctl command + if let Ok(output) = Command::new("/usr/sbin/smartctl") + .arg("--scan") // scan for devices + .arg("--json") // give us the result in JSON + .output() + { + // if we can convert that output into a sensible utf8 string + if let Ok(stdout) = String::from_utf8(output.stdout) { + // if we can parse that result into a JSON value + if let Ok(result) = serde_json::from_str(&stdout) { + // give the output to the caller + return result; + } + } + } + + // give the caller an empty object + json!("{}") +} + +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- + +fn router() -> Router { + build_simple_router(|route| { + route.resource::("smartctl"); + }) +} + +pub fn main() { + // initialize logging, configured by environment + env_logger::init(); + // start the service + let addr = "0.0.0.0:8080"; + info!("Listening for requests at http://{}", addr); + let _ = gotham::start(addr, router()); +}