From 5e51050fa3fed62e701fa52fedbc1be94bd76a87 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sun, 18 Jun 2023 06:15:31 +0000 Subject: [PATCH] initialize monitor client --- Cargo.lock | 5 ++ lib/rs_client/Cargo.toml | 5 ++ lib/rs_client/src/lib.rs | 139 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95d239a55..72a373047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1439,8 +1439,13 @@ dependencies = [ name = "monitor_client" version = "1.0.0" dependencies = [ + "anyhow", + "envy", "monitor_types", "reqwest", + "resolver_api", + "serde", + "serde_json", ] [[package]] diff --git a/lib/rs_client/Cargo.toml b/lib/rs_client/Cargo.toml index 544f7be2a..0410bdd1f 100644 --- a/lib/rs_client/Cargo.toml +++ b/lib/rs_client/Cargo.toml @@ -10,3 +10,8 @@ license.workspace = true [dependencies] monitor_types.workspace = true reqwest.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +resolver_api.workspace = true +envy.workspace = true \ No newline at end of file diff --git a/lib/rs_client/src/lib.rs b/lib/rs_client/src/lib.rs index eebcebc2d..d11f44eba 100644 --- a/lib/rs_client/src/lib.rs +++ b/lib/rs_client/src/lib.rs @@ -1,7 +1,23 @@ +use anyhow::{anyhow, Context}; +use monitor_types::requests::auth::{self, LoginLocalUserResponse, LoginWithSecretResponse}; +use reqwest::StatusCode; +use resolver_api::HasResponse; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::json; + +#[derive(Deserialize)] +struct MonitorEnv { + monitor_url: String, + monitor_token: Option, + monitor_username: Option, + monitor_password: Option, + monitor_secret: Option, +} + pub struct MonitorClient { reqwest: reqwest::Client, address: String, - token: String, + jwt: String, } impl MonitorClient { @@ -9,9 +25,126 @@ impl MonitorClient { MonitorClient { reqwest: Default::default(), address: address.into(), - token: token.into(), + jwt: token.into(), } } - pub fn new_with_credentials() {} + pub async fn new_with_password( + address: impl Into, + username: impl Into, + password: impl Into, + ) -> anyhow::Result { + let mut client = MonitorClient { + reqwest: Default::default(), + address: address.into(), + jwt: Default::default(), + }; + + let LoginLocalUserResponse { jwt } = client + .auth(auth::LoginLocalUser { + username: username.into(), + password: password.into(), + }) + .await?; + + client.jwt = jwt; + + Ok(client) + } + + pub async fn new_with_secret( + address: impl Into, + username: impl Into, + secret: impl Into, + ) -> anyhow::Result { + let mut client = MonitorClient { + reqwest: Default::default(), + address: address.into(), + jwt: Default::default(), + }; + + let LoginWithSecretResponse { jwt } = client + .auth(auth::LoginWithSecret { + username: username.into(), + secret: secret.into(), + }) + .await?; + + client.jwt = jwt; + + Ok(client) + } + + pub async fn new_from_env() -> anyhow::Result { + let env = envy::from_env::() + .context("failed to parse environment for monitor client")?; + if let Some(token) = env.monitor_token { + Ok(MonitorClient::new_with_token(&env.monitor_url, token)) + } else if let Some(password) = env.monitor_password { + let username = env.monitor_username.ok_or(anyhow!( + "must provide MONITOR_USERNAME to authenticate with MONITOR_PASSWORD" + ))?; + MonitorClient::new_with_password(&env.monitor_url, username, password).await + } else if let Some(secret) = env.monitor_secret { + let username = env.monitor_username.ok_or(anyhow!( + "must provide MONITOR_USERNAME to authenticate with MONITOR_SECRET" + ))?; + MonitorClient::new_with_secret(&env.monitor_url, username, secret).await + } else { + Err(anyhow!("failed to initialize monitor client from env | must provide one of: (MONITOR_TOKEN), (MONITOR_USERNAME and MONITOR_PASSWORD), (MONITOR_USERNAME and MONITOR_SECRET)")) + } + } + + pub async fn api(&self, request: T) -> anyhow::Result { + let req_type = T::req_type(); + self.post( + "/api", + json!({ + "type": req_type, + "params": request + }), + ) + .await + } + + pub async fn auth(&self, request: T) -> anyhow::Result { + let req_type = T::req_type(); + self.post( + "/auth", + json!({ + "type": req_type, + "params": request + }), + ) + .await + } + + async fn post( + &self, + endpoint: &str, + body: impl Into>, + ) -> anyhow::Result { + let req = self + .reqwest + .post(format!("{}{endpoint}", self.address)) + .header("Authorization", format!("Bearer {}", self.jwt)); + let req = if let Some(body) = body.into() { + req.header("Content-Type", "application/json").json(&body) + } else { + req + }; + let res = req.send().await.context("failed to reach monitor api")?; + let status = res.status(); + if status == StatusCode::OK { + match res.json().await { + Ok(res) => Ok(res), + Err(e) => Err(anyhow!("{status}: {e:#?}")), + } + } else { + match res.text().await { + Ok(res) => Err(anyhow!("{status}: {res}")), + Err(e) => Err(anyhow!("{status}: {e:#?}")), + } + } + } }