diff options
author | Adam Carpenter <adam.carpenter@adp.com> | 2021-12-10 14:15:06 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-10 14:15:06 -0500 |
commit | d3e1b697fec543e0197f1c549164c210504dde3b (patch) | |
tree | e10ec339108a8f8dbd2dd0f4737d580a0fc26ac1 /angelsharkd/src/routes | |
parent | 3eb745f153873d537bdc788170fc878543e7899e (diff) | |
parent | f98a1fa3035fb4834441ead1cce6710739312e14 (diff) | |
download | altruistic-angelshark-d3e1b697fec543e0197f1c549164c210504dde3b.tar.xz altruistic-angelshark-d3e1b697fec543e0197f1c549164c210504dde3b.zip |
Merge pull request #6 from adpllc/extensions/simple_deprov
Simple Deprov Extension
Diffstat (limited to 'angelsharkd/src/routes')
5 files changed, 136 insertions, 12 deletions
diff --git a/angelsharkd/src/routes/extensions/mod.rs b/angelsharkd/src/routes/extensions/mod.rs index cada47f..54053af 100644 --- a/angelsharkd/src/routes/extensions/mod.rs +++ b/angelsharkd/src/routes/extensions/mod.rs @@ -8,7 +8,7 @@ mod simple_search; /// The extension filter; consists of all compiled optional Angelshark extension /// filters combined under `/extensions`. -pub fn filter(_config: &Config) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { +pub fn filter(config: &Config) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { // Note: this next line deals with the common edge case of having no // extensions loaded with feature flags. It ensures that the the type // checking is right when the return `.and()` is called below. @@ -17,14 +17,14 @@ pub fn filter(_config: &Config) -> impl Filter<Extract = impl Reply, Error = Rej // Block to enable simple_search extension feature. Instantiates a // searchable haystack and configures filters to handle search requests. #[cfg(feature = "simple_search")] - let haystack = simple_search::Haystack::new(_config.runner.clone()); + let haystack = simple_search::Haystack::new(config.runner.clone()); #[cfg(feature = "simple_search")] let filters = filters .or(simple_search::search_filter(haystack.clone())) .or(simple_search::refresh_filter(haystack)); #[cfg(feature = "simple_deprov")] - let filters = filters.or(simple_deprov::filter()); + let filters = filters.or(simple_deprov::filter(config.runner.clone())); path("extensions").and(filters) } diff --git a/angelsharkd/src/routes/extensions/simple_deprov.rs b/angelsharkd/src/routes/extensions/simple_deprov.rs deleted file mode 100644 index 2b5ad40..0000000 --- a/angelsharkd/src/routes/extensions/simple_deprov.rs +++ /dev/null @@ -1,5 +0,0 @@ -use warp::{Filter, Rejection, Reply}; - -pub fn filter() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { - warp::path("deprov").map(|| -> &str { todo!() }) // TODO: -} diff --git a/angelsharkd/src/routes/extensions/simple_deprov/README.md b/angelsharkd/src/routes/extensions/simple_deprov/README.md new file mode 100644 index 0000000..4900075 --- /dev/null +++ b/angelsharkd/src/routes/extensions/simple_deprov/README.md @@ -0,0 +1,61 @@ +# Daemon Extension `simple_deprov` + +This extension implements excruciatingly simple extension de-provisioning. For +example, if an agent was provisioned with a `station-user` and an +`agent-loginid`, you can submit those extensions, their type, and the ACM they +were provision on. They relevant commands to remove those objects will be +executed in parallel, and any errors encountered will be returned. + +## Getting Started + +To enable this feature, compile `angelsharkd` with the `simple_deprov` flag: + +```sh +cargo build --bin angelsharkd --features simple_deprov ... +``` + +## `POST /extensions/deprov` Remove Objects + +The request type is TODO: + +```json +POST /extensions/deprov +[ + { + "station-user": { + "acm": "01", + "ext": "17571230000" + } + }, + { + "agent-loginid": { + "acm": "01", + "ext": "17571240000" + } + } +] +``` + +If all of the deprov commands were successful, the response is an empty array. + +```json +200 OK +[] +``` + +If there were errors running the relevant deprov commands (such as when an +extension does not exist), they are included in the resulting array. + +```json +200 OK +[ + "ACM lab: 1 00000000 309e Extension exists but assigned to a different object", + "ACM lab: 1 00000000 2ed5 Extension assigned as remote extension on the uniform-dialplan form", + "ACM lab: 1 00000000 2ed5 Extension assigned as remote extension on the uniform-dialplan form" +] +``` + +## Logging + +The `deprov` endpoint always returns successfully. Any errors encountered during +the command execution are logged as `ERROR`. diff --git a/angelsharkd/src/routes/extensions/simple_deprov/mod.rs b/angelsharkd/src/routes/extensions/simple_deprov/mod.rs new file mode 100644 index 0000000..fc734f3 --- /dev/null +++ b/angelsharkd/src/routes/extensions/simple_deprov/mod.rs @@ -0,0 +1,70 @@ +use libangelshark::{AcmRunner, Message, ParallelIterator}; +use log::error; +use serde::Deserialize; +use std::convert::Infallible; +use warp::{ + body::{content_length_limit, json}, + post, reply, Filter, Rejection, Reply, +}; + +const SIXTEEN_K: u64 = 1024 * 16; + +/// Returns a warp filter to handle HTTP POSTs for deprovisioning stations, agents, etc. +pub fn filter(runner: AcmRunner) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { + warp::path("deprov") + .and(post()) + .and(content_length_limit(SIXTEEN_K)) + .and(json()) + .and_then(move |entries| remove_entries(entries, runner.to_owned())) +} + +/// Queues removal commands for [Entries] on an [AcmRunner]. Gathers any errors encountered and returns those. +async fn remove_entries(entries: Entries, mut runner: AcmRunner) -> Result<impl Reply, Infallible> { + // Construct OSSI messages to carry out removals. + for entry in entries { + match entry { + Entry::StationUser { acm, ext } => { + runner.queue_input(&acm, &Message::new(&format!("clear amw all {}", ext))); + runner.queue_input(&acm, &Message::new(&format!("remove station {}", ext))); + } + Entry::AgentLoginId { acm, ext } => { + runner.queue_input( + &acm, + &Message::new(&format!("remove agent-loginID {}", ext)), + ); + } + } + } + + // Gather any errors encountered and format them for the client response. + let errors: Vec<String> = runner + .run_cached() + .map(|(acm, output)| match output { + Ok(messages) => messages + .into_iter() + .filter_map(|message| Some(format!("ACM {}: {}", acm.clone(), message.error?))) + .collect(), + Err(error) => vec![format!("ACM {}: {}", acm, error)], + }) + .flatten() + .collect(); + + // Log errors for tracking. + for error in &errors { + error!("{}", error); + } + + Ok(reply::json(&errors)) +} + +/// Collection of [Entry]. +type Entries = Vec<Entry>; + +/// Very basic [Deserialize] target for deprov inputs. Going from stringly typed to strongly typed. +#[derive(Debug, Deserialize)] +enum Entry { + #[serde(rename(deserialize = "station-user"))] + StationUser { acm: String, ext: String }, + #[serde(rename(deserialize = "agent-loginid"))] + AgentLoginId { acm: String, ext: String }, +} diff --git a/angelsharkd/src/routes/extensions/simple_search/mod.rs b/angelsharkd/src/routes/extensions/simple_search/mod.rs index 5b5dc8e..159137a 100644 --- a/angelsharkd/src/routes/extensions/simple_search/mod.rs +++ b/angelsharkd/src/routes/extensions/simple_search/mod.rs @@ -21,7 +21,7 @@ pub fn search_filter( .and(post()) .and(content_length_limit(1024 * 16)) .and(json()) - .and_then(move |terms: Needles| search(haystack.to_owned(), terms)) + .and_then(move |needles: Needles| search(haystack.to_owned(), needles)) .with(with::header(header::PRAGMA, "no-cache")) .with(with::header(header::CACHE_CONTROL, "no-store, max-age=0")) .with(with::header(header::X_FRAME_OPTIONS, "DENY")) @@ -39,8 +39,6 @@ pub fn refresh_filter( /// Runs the search request to find all needles in the haystack and converts the /// results into a reply. async fn search(haystack: Haystack, needles: Needles) -> Result<impl Reply, Infallible> { - // Ok(haystack.search(Vec::new())?) - // if let Ok(matches = haystack.search(needle); match haystack.search(needles) { Ok(matches) => Ok(reply::with_status(reply::json(&matches), StatusCode::OK)), Err(e) => Ok(reply::with_status( @@ -56,7 +54,7 @@ async fn refresh(haystack: Haystack) -> Result<impl Reply, Infallible> { // Run refresh as a background task and immediately return. tokio::spawn(async move { if let Err(e) = haystack.refresh() { - error!("{}", e.to_string()); // TODO: use logger + error!("{}", e.to_string()); } else { info!("Search haystack refreshed."); } |