use core::ffi::c_char;
use libloading::Library;
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io;
use std::ptr;
use tracing::{debug, error, warn};
pub mod models;
use crate::models::*;
fn compute_sha256_hash(file_path: &str) -> Result<String, io::Error> {
let mut file = File::open(file_path)?;
let mut hasher = Sha256::new();
io::copy(&mut file, &mut hasher)?;
let hash = hasher.finalize();
Ok(format!("{:x}", hash))
}
fn validate_hash(file_path: &str, expected_hash: &str) -> Result<(), String> {
debug!("expected hash value: {}", expected_hash);
if "any".eq(expected_hash) {
warn!("Validation of shared library's hash value has been disabled! Make sure that the shared library is what you expect it to be!");
return Ok(());
}
let computed_hash =
compute_sha256_hash(file_path).map_err(|e| format!("Error computing hash: {}", e))?;
if computed_hash == expected_hash {
Ok(())
} else {
Err(format!(
"Hash mismatch! Expected: {}, Got: {}",
expected_hash, computed_hash
))
}
}
pub trait ITargetProvider: Send + Sync {
fn get_validation_rule(&self) -> Result<ValidationRule, String>;
fn get(
&self,
deployment: DeploymentSpec,
references: Vec<ComponentStep>,
) -> Result<Vec<ComponentSpec>, String>;
fn apply(
&self,
deployment: DeploymentSpec,
step: DeploymentStep,
is_dry_run: bool,
) -> Result<HashMap<String, ComponentResultSpec>, String>;
}
#[repr(C)]
pub struct ProviderHandle {
provider: Box<dyn ITargetProvider>,
lib: Library,
}
pub struct ProviderWrapper {
pub inner: Box<dyn ITargetProvider>,
}
#[no_mangle]
pub unsafe extern "C" fn create_provider_instance(
provider_path: *const c_char,
expected_hash: *const c_char,
config_json: *const c_char,
) -> *mut ProviderHandle {
if tracing_subscriber::fmt::try_init().is_err() {
}
let Ok(provider_path) = unsafe { CStr::from_ptr(provider_path) }.to_str() else {
error!("Path to shared library is not valid UTF-8");
return ptr::null_mut();
};
let Ok(expected_hash) = unsafe { CStr::from_ptr(expected_hash) }.to_str() else {
error!("Hash value is not valid UTF-8");
return ptr::null_mut();
};
if let Err(err) = validate_hash(provider_path, expected_hash) {
error!("Hash validation failed: {}", err);
return ptr::null_mut();
}
let Ok(lib) = (unsafe {
Library::new(provider_path).inspect_err(|err| {
error!("Failed to load provider library [{}]: {err}", provider_path);
})
}) else {
return ptr::null_mut();
};
let Ok(newly_created_provider_wrapper) = (unsafe {
type CreateProviderFn = unsafe extern "C" fn(*const c_char) -> *mut ProviderWrapper;
lib.get::<CreateProviderFn>(b"create_provider\0")
.map(|create_provider| create_provider(config_json))
}) else {
error!(
"Shared library [{}] does not contain required symbol: create_provider",
provider_path
);
return ptr::null_mut();
};
if newly_created_provider_wrapper.is_null() {
error!("Error: create_provider returned null pointer");
return ptr::null_mut();
}
let provider = unsafe { Box::from_raw(newly_created_provider_wrapper).inner };
let handle = Box::new(ProviderHandle {
provider, lib,
});
Box::into_raw(handle)
}
#[no_mangle]
pub unsafe extern "C" fn destroy_provider_instance(handle: *mut ProviderHandle) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub unsafe extern "C" fn get_validation_rule(handle: *mut ProviderHandle) -> *mut c_char {
let handle = unsafe { &*handle };
match handle.provider.get_validation_rule() {
Ok(validation_rule) => {
match CString::new(serde_json::to_string(&validation_rule).unwrap()) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn get(
handle: *mut ProviderHandle,
deployment_json: *const c_char,
references_json: *const c_char,
) -> *mut c_char {
let handle = unsafe { &*handle };
let deployment_str = unsafe { CStr::from_ptr(deployment_json).to_str().unwrap() };
let references_str = unsafe { CStr::from_ptr(references_json).to_str().unwrap() };
let deployment: DeploymentSpec = serde_json::from_str(deployment_str).unwrap();
let references: Vec<ComponentStep> = serde_json::from_str(references_str).unwrap();
match handle.provider.get(deployment, references) {
Ok(components) => {
let json = serde_json::to_string(&components).unwrap();
CString::new(json).unwrap().into_raw()
}
Err(err) => {
debug!("Error getting components: {err}");
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn apply(
handle: *mut ProviderHandle,
deployment_json: *const c_char,
step_json: *const c_char,
is_dry_run: i32,
) -> *mut c_char {
let handle = unsafe { &*handle };
let deployment_str = unsafe { CStr::from_ptr(deployment_json).to_str().unwrap() };
let step_str = unsafe { CStr::from_ptr(step_json).to_str().unwrap() };
let deployment: DeploymentSpec = serde_json::from_str(deployment_str).unwrap();
let step: DeploymentStep = serde_json::from_str(step_str).unwrap();
let is_dry_run = is_dry_run != 0;
match handle.provider.apply(deployment, step, is_dry_run) {
Ok(result_map) => {
let json = serde_json::to_string(&result_map).unwrap();
CString::new(json).unwrap().into_raw()
}
Err(err) => {
debug!("Error applying changes: {err}");
ptr::null_mut()
}
}
}