Compare commits

...

8 Commits

Author SHA1 Message Date
brotoskyj f3c1d97d28 Implemented Threaded Rust Process
Build Library / Build Library (push) Successful in 6m18s
It still locks the GIL, but this is the ground work for the non blocking GUI. Python objects are converted to Rust objects in the beginning of the function call so they can be sent to threads safely.
2025-12-02 10:53:25 -05:00
brotoskyj 99d7b5f74e Merge remote-tracking branch 'refs/remotes/origin/RustImplementation' into RustImplementation 2025-11-25 15:30:07 -05:00
brotoskyj 6327adeabf Refactor of Loxide Libs
Removed spaces, imports, and converters from code
2025-11-25 15:29:55 -05:00
brotoskyj b289e9324c Slight Changes
Build Library / Build Library (push) Successful in 5m15s
Refactor of Loxide Libs
Removed spaces, imports, and converters from code
2025-11-25 15:27:16 -05:00
brotoskyj a80c2ca1e1 Features
Build Library / Build Library (push) Successful in 4m46s
closes #34
Removed a lot of unwraps, most remaining unwraps will likely stay in the code, as it will be expected behavior to panic and crash rather than a system level abort/exit event
2025-11-24 11:36:50 -05:00
brotoskyj dd206eb272 Feature
closes #37
2025-11-24 10:35:38 -05:00
brotoskyj 87ab1e3b28 Telemetry Config Change
Build Library / Build Library (push) Successful in 4m54s
Added implementation instead of separate function to load Telemetry configuration
2025-11-21 14:00:09 -05:00
brotoskyj 303ecd8368 Removed a lot of unwraps
Build Library / Build Library (push) Successful in 5m2s
Removing the unwraps now has better error handling and will cause the program to crash with crash dumps instead of panic!
2025-11-21 12:20:11 -05:00
6 changed files with 820 additions and 499 deletions
+1
View File
@@ -1,3 +1,4 @@
/target /target
build.sh build.sh
pythontest.py pythontest.py
changelog.md
+536 -297
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "airlock_libs" name = "airlock_libs"
version = "3.1.2" version = "5.0.0"
edition = "2024" edition = "2024"
[lib] [lib]
@@ -24,6 +24,7 @@ tonic = { version = "0.8.2", features = ["tls-roots"] }
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.20" tracing-subscriber = "0.3.20"
tracing-opentelemetry = "0.32.0" tracing-opentelemetry = "0.32.0"
pyo3-async-runtimes = { version = "0.27.0", features = ["async-std", "tokio"] }
[package.metadata.maturin] [package.metadata.maturin]
generate-abi-stubs = true generate-abi-stubs = true
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project] [project]
name = "airlock_libs" name = "airlock_libs"
version = "3.1.2" version = "5.0.0"
description = "Airlock Digital API Wrapper" description = "Airlock Digital API Wrapper"
readme = "README.md" readme = "README.md"
license = { text = "AGPL-3.0-only" } license = { text = "AGPL-3.0-only" }
+141 -61
View File
@@ -9,6 +9,7 @@ use opentelemetry::{Context, KeyValue, sdk::trace as sdktrace, trace::Tracer};
use opentelemetry::{Key, global}; use opentelemetry::{Key, global};
use opentelemetry_otlp::WithExportConfig; use opentelemetry_otlp::WithExportConfig;
use pyo3::{prelude::*, types::PyString}; use pyo3::{prelude::*, types::PyString};
use pyo3_async_runtimes::async_std;
use reqwest::{ use reqwest::{
Client, Client,
header::{HeaderMap, HeaderName, HeaderValue}, header::{HeaderMap, HeaderName, HeaderValue},
@@ -25,12 +26,34 @@ use std::{
str::FromStr, str::FromStr,
}; };
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct TelemetryConfig { struct TelemetryConfig {
TELEMETRY: bool, TELEMETRY: bool,
TELEM_URL: Option<String>, TELEM_URL: Option<String>,
} }
impl TelemetryConfig {
pub fn load() -> Self {
let cfg_path = get_base_directory().join("config\\user_config.json");
if !cfg_path.exists() {
return Self {
TELEMETRY: false,
TELEM_URL: None,
};
}
match fs::read_to_string(&cfg_path) {
Ok(contents) => serde_json::from_str::<Self>(&contents).unwrap_or(Self {
TELEMETRY: false,
TELEM_URL: None,
}),
Err(_) => Self {
TELEMETRY: false,
TELEM_URL: None,
},
}
}
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
struct ApiResponse { struct ApiResponse {
error: String, error: String,
@@ -66,6 +89,40 @@ struct Group {
localip: String, localip: String,
} }
enum ExtractedValues {
Headers(reqwest::header::HeaderMap),
BaseUrl(String),
}
trait Converter {
fn convert(py: Python<'_>, py_self: &Py<PyAny>, extract_headers: bool) -> ExtractedValues;
}
struct PyData;
impl Converter for PyData {
fn convert(py: Python<'_>, py_self: &Py<PyAny>, extract_headers: bool) -> ExtractedValues {
if extract_headers {
let headers = py_self.getattr(py, "headers").unwrap().to_string();
let headers_replace = headers.replace('\'', "\"");
let parsed: Value = serde_json::from_str(headers_replace.as_str()).unwrap();
let mut header_map = HeaderMap::new();
if let Some(obj) = parsed.as_object() {
for (_key, value) in obj {
if let Some(v) = value.as_str() {
let val = HeaderValue::from_str(v).unwrap();
header_map.insert(HeaderName::from_str("X-APIKey").unwrap(), val);
}
}
}
ExtractedValues::Headers(header_map)
} else {
let base_url = py_self.getattr(py, "base_url").unwrap().to_string();
ExtractedValues::BaseUrl(base_url)
}
}
}
#[pyfunction] #[pyfunction]
pub fn pull_policy_exec_histories( pub fn pull_policy_exec_histories(
py: Python<'_>, py: Python<'_>,
@@ -74,7 +131,22 @@ pub fn pull_policy_exec_histories(
exec_types: String, exec_types: String,
days: i64, days: i64,
) -> Py<PyString> { ) -> Py<PyString> {
let rt = tokio::runtime::Runtime::new().unwrap(); let headers: HeaderMap = match PyData::convert(py, &py_self, true) {
ExtractedValues::Headers(h) => h,
ExtractedValues::BaseUrl(_) => std::process::abort(),
};
let base_url = match PyData::convert(py, &py_self, false) {
ExtractedValues::Headers(_) => std::process::abort(),
ExtractedValues::BaseUrl(b) => b,
};
let handle = std::thread::spawn(move || {
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
println!("Failed to build Tokio Runtime: {:?}", e);
std::process::abort();
}
};
rt.block_on(async { rt.block_on(async {
let _ = init_tracer(); let _ = init_tracer();
}); });
@@ -86,13 +158,25 @@ pub fn pull_policy_exec_histories(
) )
.into(); .into();
let writeable_filepath = file_path.clone(); let writeable_filepath = file_path.clone();
if !file_path.exists() { if !&file_path.exists() {
if let Some(parent_dir) = file_path.parent() if let Some(parent_dir) = &file_path.parent()
&& !parent_dir.exists() && !parent_dir.exists()
{ {
fs::create_dir_all(parent_dir).unwrap(); match fs::create_dir_all(parent_dir) {
Ok(_) => {}
Err(e) => {
println!("Failed to Create Directory {:?}: {}", parent_dir, e);
std::process::abort();
}
}
}
match fs::File::create(&file_path) {
Ok(_) => {}
Err(e) => {
println!("Failed to Create Directory {:?}: {}", &file_path, e);
std::process::abort();
}
} }
fs::File::create(file_path).unwrap();
} }
let data = ApiResponse { let data = ApiResponse {
error: "Success".to_string(), error: "Success".to_string(),
@@ -101,7 +185,13 @@ pub fn pull_policy_exec_histories(
}, },
}; };
let data_write = serde_json::to_string_pretty(&data).expect("Failed to serialize"); let data_write = serde_json::to_string_pretty(&data).expect("Failed to serialize");
fs::write(writeable_filepath.clone(), data_write).unwrap(); match fs::write(writeable_filepath.clone(), data_write) {
Ok(_) => {}
Err(e) => {
println!("Failed to write to: {:?}: {}", &writeable_filepath, e);
std::process::abort();
}
}
let mut checkpoint_number: String = skipback(days).to_string(); let mut checkpoint_number: String = skipback(days).to_string();
let multi_progress = MultiProgress::new(); let multi_progress = MultiProgress::new();
multi_progress.set_draw_target(ProgressDrawTarget::stdout()); multi_progress.set_draw_target(ProgressDrawTarget::stdout());
@@ -113,7 +203,7 @@ pub fn pull_policy_exec_histories(
); );
progress_bar.enable_steady_tick(std::time::Duration::from_millis(100)); progress_bar.enable_steady_tick(std::time::Duration::from_millis(100));
let client = tracer.in_span("Building HTTP Client", |cx| { let client = tracer.in_span("Building HTTP Client", |cx| {
let client_result = build_client(py, &py_self); let client_result = build_client(headers);
match client_result { match client_result {
Ok(client_result) => { Ok(client_result) => {
cx.span().add_event( cx.span().add_event(
@@ -135,22 +225,34 @@ pub fn pull_policy_exec_histories(
); );
cx.span() cx.span()
.set_status(Status::error("Client Failed to Build")); .set_status(Status::error("Client Failed to Build"));
panic!("Failed to Build Client: {:?}", client_result); println!("Failed to Build Client: {:?}", client_result);
std::process::abort();
} }
} }
}); });
let api: Py<PyAny> = py_self;
let cutoff = Local::now().naive_local() - Duration::days(days); let cutoff = Local::now().naive_local() - Duration::days(days);
let mut f = File::open(&writeable_filepath).unwrap(); let mut f = match File::open(&writeable_filepath) {
Ok(f) => f,
Err(e) => {
println!("Failed to Access {:?}: {}", &writeable_filepath, e);
std::process::abort();
}
};
tracer.in_span("Airlock Data Retreival", |cx| { tracer.in_span("Airlock Data Retreival", |cx| {
let span = cx.span(); let span = cx.span();
span.set_attribute(Key::new("Days").string(days.to_string().to_string())); span.set_attribute(Key::new("Days").string(days.to_string()));
span.set_attribute(KeyValue::new("Policy Name", policy_names.clone()));
loop { loop {
f.seek(SeekFrom::Start(0)).unwrap(); match f.seek(SeekFrom::Start(0)) {
Ok(_) => {}
Err(e) => {
println!("Failed to seek start of {:?}: {}", f, e);
std::process::abort();
}
}
let execution_histories = tracer.in_span(checkpoint_number.to_string(), |cx| { let execution_histories = tracer.in_span(checkpoint_number.to_string(), |cx| {
let results: ApiResponse = history_logging( let results: ApiResponse = history_logging(
py, &base_url,
&api,
&exec_types, &exec_types,
&checkpoint_number, &checkpoint_number,
&policy_names, &policy_names,
@@ -166,8 +268,8 @@ pub fn pull_policy_exec_histories(
if parsed_responses.is_empty() { if parsed_responses.is_empty() {
break; break;
} }
let mut seen: HashMap<(String, String, String), Group> = if writeable_filepath.exists() let mut seen: HashMap<(String, String, String), Group> =
{ if writeable_filepath.exists() {
let mut contents = String::new(); let mut contents = String::new();
f.read_to_string(&mut contents).unwrap(); f.read_to_string(&mut contents).unwrap();
let existing_data: ApiResponse = let existing_data: ApiResponse =
@@ -226,7 +328,12 @@ pub fn pull_policy_exec_histories(
}, },
}; };
let data_write = serde_json::to_string_pretty(&final_response).unwrap(); let data_write = serde_json::to_string_pretty(&final_response).unwrap();
fs::write(&writeable_filepath, data_write).unwrap(); match fs::write(&writeable_filepath, data_write) {
Ok(_) => {}
Err(e) => {
println!("Failed to write to: {:?}: {}", &writeable_filepath, e);
}
}
if let Some(last_item) = &final_response.response.exechistories.last() if let Some(last_item) = &final_response.response.exechistories.last()
&& let Ok(last_date) = NaiveDate::parse_from_str( && let Ok(last_date) = NaiveDate::parse_from_str(
&last_item.datetime.replace(" +0000 UTC", ""), &last_item.datetime.replace(" +0000 UTC", ""),
@@ -234,49 +341,44 @@ pub fn pull_policy_exec_histories(
) )
{ {
let date_diff = Local::now().naive_local().date() - last_date; let date_diff = Local::now().naive_local().date() - last_date;
let percentage_diff = (days - date_diff.num_days()) as f64 / days as f64 * 100.0; let percentage_diff =
(days - date_diff.num_days()) as f64 / days as f64 * 100.0;
progress_bar.set_position(percentage_diff.round() as u64); progress_bar.set_position(percentage_diff.round() as u64);
progress_bar.set_message("Total Percent Complete"); progress_bar.set_message("Total Percent Complete");
} }
} }
}); });
progress_bar.finish_with_message("All Checkpoints Complete"); progress_bar.finish_with_message("All Checkpoints Complete");
let return_data = fs::read_to_string(&writeable_filepath).unwrap(); let return_data = match fs::read_to_string(&writeable_filepath) {
Ok(return_data) => return_data,
Err(e) => {
println!("Failed to read data from: {:?}: {}", &writeable_filepath, e);
std::process::abort();
}
};
shutdown_tracer_provider(); shutdown_tracer_provider();
PyString::new(py, &return_data).into() return_data.to_string()
} });
let gil_value = handle.join().unwrap();
fn build_client(py: Python<'_>, py_self: &Py<PyAny>) -> Result<reqwest::Client, reqwest::Error> { Python::attach(|py| PyString::new(py, &gil_value).into())
let headers = py_self.getattr(py, "headers").unwrap().to_string();
let headers_replace = headers.replace('\'', "\"");
let parsed: Value = serde_json::from_str(headers_replace.as_str()).unwrap();
let mut header_map = HeaderMap::new();
if let Some(obj) = parsed.as_object() {
for (_key, value) in obj {
if let Some(v) = value.as_str() {
let val = HeaderValue::from_str(v).unwrap();
header_map.insert(HeaderName::from_str("X-APIKey").unwrap(), val);
}
}
} }
fn build_client(headers: HeaderMap) -> Result<reqwest::Client, reqwest::Error> {
Client::builder() Client::builder()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.default_headers(header_map) .default_headers(headers)
.timeout(std::time::Duration::from_secs(300)) .timeout(std::time::Duration::from_secs(300))
.build() .build()
} }
#[tokio::main] #[tokio::main]
async fn history_logging( async fn history_logging(
py: Python<'_>, base_url: &String,
py_self: &Py<PyAny>,
exec_types: &String, exec_types: &String,
checkpoint_number: &String, checkpoint_number: &String,
policy_names: &String, policy_names: &String,
client: &Client, client: &Client,
) -> ApiResponse { ) -> ApiResponse {
let base_url = py_self.getattr(py, "base_url").unwrap().to_string();
let payload = format!( let payload = format!(
r#"{{ r#"{{
"type": {}, "type": {},
@@ -334,30 +436,8 @@ fn skipback(days: i64) -> ObjectId {
ObjectId::parse_str(&objectid_hex).expect("Invalid ObjectId hex") ObjectId::parse_str(&objectid_hex).expect("Invalid ObjectId hex")
} }
fn load_telemetry_config() -> TelemetryConfig {
let cfg_path = get_base_directory().join("config\\user_config.json");
if !cfg_path.exists() {
return TelemetryConfig {
TELEMETRY: false,
TELEM_URL: None,
};
}
match fs::read_to_string(&cfg_path) {
Ok(contents) => {
serde_json::from_str::<TelemetryConfig>(&contents).unwrap_or(TelemetryConfig {
TELEMETRY: false,
TELEM_URL: None,
})
}
Err(_) => TelemetryConfig {
TELEMETRY: false,
TELEM_URL: None,
},
}
}
fn init_tracer() -> Result<Option<sdktrace::Tracer>, TraceError> { fn init_tracer() -> Result<Option<sdktrace::Tracer>, TraceError> {
let cfg = load_telemetry_config(); let cfg = TelemetryConfig::load();
if !cfg.TELEMETRY { if !cfg.TELEMETRY {
global::set_tracer_provider(NoopTracerProvider::new()); global::set_tracer_provider(NoopTracerProvider::new());
return Ok(None); return Ok(None);
+1 -1
View File
@@ -11,4 +11,4 @@ urllib3==2.5.0
pyperclip==1.11.0 pyperclip==1.11.0
--extra-index-url https://git.racooncity.org/api/packages/brotoskyj/pypi/simple/ --extra-index-url https://git.racooncity.org/api/packages/brotoskyj/pypi/simple/
airlock_libs==3.1.2 airlock_libs==5.0.0