Skip to content

Commit

Permalink
Merge pull request #4 from tinythings/isbm-checkbook-orphans
Browse files Browse the repository at this point in the history
Implement module calls by entities
  • Loading branch information
isbm authored Oct 4, 2024
2 parents b2cb4ef + 57ab66a commit 17b230a
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 41 deletions.
21 changes: 21 additions & 0 deletions docs/modeldescr/layout.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Model description index file has the following structure:
This is a description of this model
that gives you more idea what it is etc.
maintainer: John Smith <john.smith@example.com>
checkbook: null
config: null
The following fields are supported:
Expand All @@ -76,3 +77,23 @@ The following fields are supported:

Global configuration section. It is applied to the whole session, globally. However
different model can have a different configuration.

``checkbook``

Checkbook is a list of sections that groups relations those needs to be checked.
An example:

.. code-block:: yaml
checkbook:
my_label:
- relation-one
- relation-two
my_other_label:
- relation-one
- relation-three
In this case user can call ``my_label`` and SysInspect will only go through relations,
grouped inside that section, leaving all other untouched. If checkbook is omitted,
then all relations will be examined, one after another.
8 changes: 8 additions & 0 deletions examples/models/router/actions/process-actions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ actions:
- udevd
- journald
state:
test:
args:
- foo: bar
- some: "other,stuff"
$:
opts:
- one
- two
- "3"
args:
# Variable @(foo.bar) is contextual to the entity
# and starts at current entity root.
Expand Down
16 changes: 13 additions & 3 deletions examples/models/router/model.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ maintainer: John Smith <js@javascriptsucks.com>
# Check book is a list of relation IDs for evaluation and test cases
# those needs to be performed in order to check the system
checkbook:
- logging
- general-network
# A label of examination area
network:
- logging
- general-network

system:
- systemd
- syslog

# Global model configuration
config: null
config:
modules: /tmp/sysinspect/modules
foo:
- bar
- baz
87 changes: 83 additions & 4 deletions libsysinspect/src/intp/actions.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
use super::{actproc::modfinder::ModCall, inspector::SysInspector};
use crate::SysinspectError;
use colored::Colorize;
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Display};

#[derive(Debug, Serialize, Deserialize, Default)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ModArgs {
opts: Option<Vec<String>>,
args: Option<Vec<HashMap<String, String>>>,
}

#[derive(Debug, Serialize, Deserialize, Default)]
impl ModArgs {
/// Get pairs of keyword args
pub fn args(&self) -> Vec<(String, String)> {
let mut out = Vec::<(String, String)>::default();
if let Some(argset) = &self.args {
for kwargs in argset {
for (k, v) in kwargs {
out.push((k.to_owned(), v.to_owned()));
}
}
}
out
}

/// Get options
pub fn opts(&self) -> Vec<String> {
let mut out = Vec::<String>::default();
if let Some(optset) = &self.opts {
for opt in optset {
out.push(opt.to_owned());
}
}
out
}
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Action {
id: Option<String>,
description: Option<String>,
module: String,
bind: Vec<String>,
state: HashMap<String, ModArgs>,
call: Option<ModCall>,
}

impl Action {
Expand All @@ -37,8 +66,58 @@ impl Action {
Ok(instance)
}

/// Get action's `id`
/// Get action's `id` field
pub fn id(&self) -> String {
self.id.to_owned().unwrap_or("".to_string())
}

/// Get action's `description` field
pub fn descr(&self) -> String {
self.description.to_owned().unwrap_or(format!("Action {}", self.id()))
}

/// Returns true if an action has a bind to an entity via its `eid` _(entity Id)_.
pub fn binds_to(&self, eid: &str) -> bool {
self.bind.contains(&eid.to_string())
}

pub fn run(&self) {
if let Some(call) = &self.call {
log::debug!("Calling action {} on state {}", self.id().yellow(), call.state().yellow());
call.run();
}
}

/// Setup and activate an action and is done by the Inspector.
/// This method finds module, sets up its parameters, binds constraint etc.
pub(crate) fn setup(&mut self, inspector: &SysInspector, state: String) -> Result<Action, SysinspectError> {
let mpath = inspector.cfg().get_module(&self.module)?;
if let Some(mod_args) = self.state.get(&state) {
let mut modcall = ModCall::default().set_state(state).set_module(mpath);
for (kw, arg) in &mod_args.args() {
modcall.add_kwargs(kw.to_owned(), arg.to_owned());
}

for opt in &mod_args.opts() {
modcall.add_opt(opt.to_owned());
}
self.call = Some(modcall);
}
Ok(self.to_owned())
}
}

impl Display for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<Action> - Id: {}, Descr: {}, Module: {}, Active: {}",
self.id(),
self.descr(),
self.module,
self.call.is_some()
)?;

Ok(())
}
}
1 change: 1 addition & 0 deletions libsysinspect/src/intp/actproc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod modfinder;
62 changes: 62 additions & 0 deletions libsysinspect/src/intp/actproc/modfinder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Display, path::PathBuf};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ModCall {
state: String,
module: PathBuf,
args: Vec<HashMap<String, String>>,
opts: Vec<String>,
}

impl ModCall {
/// Set state
pub fn set_state(mut self, state: String) -> Self {
self.state = state;
self
}

/// Set resolved module physical path
pub fn set_module(mut self, modpath: PathBuf) -> Self {
self.module = modpath;
self
}

/// Add a pair of kwargs
pub fn add_kwargs(&mut self, kw: String, arg: String) -> &mut Self {
self.args.push([(kw, arg)].into_iter().collect());
self
}

/// Add an option
pub fn add_opt(&mut self, opt: String) -> &mut Self {
self.opts.push(opt);
self
}

pub fn run(&self) {
log::debug!("run() of {}", self);
}

pub fn state(&self) -> String {
self.state.to_owned()
}

/// Get state ref
pub fn with_state(&self, state: String) -> bool {
self.state == state
}
}

impl Default for ModCall {
fn default() -> Self {
Self { state: "$".to_string(), module: PathBuf::default(), args: Default::default(), opts: Default::default() }
}
}

impl Display for ModCall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ModCall - State: {}, Module: {:?}, Opts: {:?}, Args: {:?}", self.state, self.module, self.opts, self.args)?;
Ok(())
}
}
66 changes: 57 additions & 9 deletions libsysinspect/src/intp/checkbook.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,66 @@
use super::entities::Entity;
use std::collections::HashMap;
use super::relations::Relation;
use serde_yaml::Value;
use std::{collections::HashMap, fmt::Display};

pub struct Checkbook {
entities: HashMap<String, Entity>,
#[derive(Debug, Default)]
pub struct CheckbookSection {
id: String,
relations: Vec<Relation>,
}

impl Checkbook {
impl CheckbookSection {
/// Initialise a checkbook.
/// Entry is a list of relations needs to be examined.
pub fn new(entry: Vec<String>) -> Self {
Checkbook { entities: HashMap::new() }.load(entry)
pub fn new(label: &Value, rel_ids: &Value, relations: &HashMap<String, Relation>) -> Option<Self> {
let mut instance = CheckbookSection::default();

// No relations defined anyway
if relations.is_empty() {
return None;
}

// Check if there is at least one requested Id in the set of relations
let mut orphans: Vec<String> = Vec::default();
if let Some(rel_ids) = rel_ids.as_sequence() {
for rid in rel_ids.iter().map(|s| s.as_str().unwrap_or("").to_string()).collect::<Vec<String>>() {
if let Some(rel) = relations.get(&rid) {
instance.relations.push(rel.to_owned());
} else {
orphans.push(rid);
}
}
instance.id = label.as_str().unwrap_or("").to_string();
}

// Checks
if instance.id.is_empty() {
log::error!("Checkbook section should have an Id");
return None;
}

// Feedback only
if !orphans.is_empty() {
log::warn!("Checkbook section \"{}\" has {} bogus relations: {}", instance.id, orphans.len(), orphans.join(", "));
}

// Discard invalid section
if instance.relations.is_empty() {
log::error!("Checkbook \"{}\" has no valid relations assotiated", instance.id);
return None;
}

Some(instance)
}

/// Get Id of the checkbook section
pub fn id(&self) -> String {
self.id.to_owned()
}
}

fn load(self, entry: Vec<String>) -> Self {
self
impl Display for CheckbookSection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<CheckbookSection - Id: {}, Relations: {:?}>", self.id, self.relations)?;
Ok(())
}
}
40 changes: 40 additions & 0 deletions libsysinspect/src/intp/conf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::SysinspectError;
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Config {
modules: PathBuf,
}

impl Config {
pub fn new(obj: &Value) -> Result<Self, SysinspectError> {
if let Ok(instance) = serde_yaml::from_value::<Config>(obj.to_owned()) {
return Ok(instance);
}

Err(SysinspectError::ModelDSLError("Unable to parse configuration".to_string()))
}

/// Get module from the namespace
pub fn get_module(&self, namespace: &str) -> Result<PathBuf, SysinspectError> {
// Fool-proof cleanup, likely a bad idea
let modpath = self.modules.join(
namespace
.trim_start_matches('.')
.trim_end_matches('.')
.trim()
.split('.')
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join("/"),
);

if !modpath.exists() {
return Err(SysinspectError::ModuleError(format!("Module \"{}\" was not found", namespace)));
}

Ok(modpath)
}
}
Loading

0 comments on commit 17b230a

Please sign in to comment.