use deno_core::{
error::{type_error, AnyError},
resolve_path, serde_json, OpState, Resource, ResourceId,
};
use mashin_ffi::{DynamicLibraryResource, NativeType, NativeValue};
use libffi::middle::Arg;
use mashin_core::{
sdk::{
ext::anyhow::{anyhow, bail},
ResourceAction, Result, Urn,
},
Client, ProviderInner, ProviderList, RawState,
};
use std::{
alloc::{dealloc, Layout},
cell::RefCell,
collections::BTreeSet,
env::{self, current_dir},
ffi::c_void,
ptr,
rc::Rc,
str::FromStr,
};
use super::state::{BackendState, FileState};
#[deno_core::op]
pub(crate) async fn as__client_new(
op_state: Rc<RefCell<OpState>>,
backend_rid: Option<ResourceId>,
) -> Result<ResourceId> {
let mut op_state = op_state.borrow_mut();
let backend = Box::new({
if let Some(backend_rid) = backend_rid {
todo!()
} else {
let path = resolve_path(".mashin", current_dir()?.as_path())?
.to_file_path()
.or_else(|_| bail!("unable to resolve state dir"))?;
BackendState::Local(FileState::new(path)?)
}
});
let backend_pointer = Box::into_raw(backend) as *mut c_void;
let rid = op_state
.resource_table
.add(AtmosphereClient::new(backend_pointer, b"mysuperpassword").await?);
op_state.put(AtmosphereState::default());
Ok(rid)
}
#[deno_core::op]
pub(crate) async fn as__client_finished(
op_state: Rc<RefCell<OpState>>,
client_rid: ResourceId,
) -> Result<()> {
let mut op_state = op_state.borrow_mut();
let atmos_client = op_state
.resource_table
.get::<AtmosphereClient>(client_rid)?;
let atmos_state = op_state.borrow_mut::<AtmosphereState>();
let backend = unsafe {
let handler = atmos_client.0.state_handler as *mut BackendState;
&*handler
};
let resources_in_storage = backend.resources().await?;
for diff in resources_in_storage.difference(&atmos_state.executed_resource) {
println!("missing: {}, probably need to delete?", diff);
}
Ok(())
}
#[deno_core::op]
pub(crate) async fn as__runtime__provider__execute(
rc_op_state: Rc<RefCell<OpState>>,
client_rid: ResourceId,
provider_rid: ResourceId,
urn_str: String,
config_raw: serde_json::Value,
dry_run: bool,
) -> Result<serde_json::Value> {
let urn = Urn::from_str(&urn_str)?;
let rc_op_state_read_only = rc_op_state.clone();
let mut op_state = rc_op_state_read_only.borrow_mut();
let atmos_client = op_state
.resource_table
.get::<AtmosphereClient>(client_rid)?;
let run_symbol = {
let resource = op_state
.resource_table
.get::<DynamicLibraryResource>(provider_rid)?;
let symbols = &resource.symbols;
*symbols
.get("run")
.ok_or_else(|| type_error("Invalid FFI symbol name"))?
.clone()
};
let atmos_state = op_state.borrow_mut::<AtmosphereState>();
let backend = unsafe {
let handler = atmos_client.0.state_handler as *mut BackendState;
&*handler
};
let provider_in_state = atmos_state
.providers
.get(&provider_rid)
.ok_or(anyhow!("invalid provider"))?;
let config_ref = Box::new(config_raw);
let config_pointer = Box::into_raw(config_ref) as *mut c_void;
let urn_ref = Box::new(urn.clone());
let urn_pointer = Box::into_raw(urn_ref) as *mut c_void;
let action_ref = Box::new(if dry_run {
ResourceAction::Get
} else {
ResourceAction::Create
});
let action_pointer = Box::into_raw(action_ref) as *mut c_void;
let current_resource_state = backend
.get(&urn)
.await?
.map(|s| s.decrypt(&atmos_client.0.key))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default();
let current_resource_state_ref = Box::new(current_resource_state);
let current_resource_state_pointer = Box::into_raw(current_resource_state_ref) as *mut c_void;
let state: RawState = unsafe {
let pointer_type = NativeType::Pointer;
let call_args = vec![
NativeValue {
pointer: provider_in_state.provider,
}
.as_arg(&pointer_type),
NativeValue {
pointer: urn_pointer,
}
.as_arg(&pointer_type),
NativeValue {
pointer: current_resource_state_pointer,
}
.as_arg(&pointer_type),
NativeValue {
pointer: config_pointer,
}
.as_arg(&pointer_type),
NativeValue {
pointer: action_pointer,
}
.as_arg(&pointer_type),
];
let provider_state = run_symbol
.cif
.call::<*mut serde_json::Value>(run_symbol.ptr, &call_args);
let state = &mut *provider_state;
let clone_state = state.clone();
ptr::drop_in_place(provider_state);
dealloc(
provider_state as *mut u8,
Layout::new::<serde_json::Value>(),
);
ptr::drop_in_place(config_pointer);
dealloc(
config_pointer as *mut u8,
Layout::new::<serde_json::Value>(),
);
ptr::drop_in_place(urn_pointer);
dealloc(urn_pointer as *mut u8, Layout::new::<Urn>());
ptr::drop_in_place(action_pointer);
dealloc(action_pointer as *mut u8, Layout::new::<ResourceAction>());
clone_state.into()
};
let encrypted = state.encrypt(&atmos_client.0.key)?;
backend.save(&urn, &encrypted).await?;
atmos_state.executed_resource.insert(urn);
Ok(state.generate_ts_output())
}
#[deno_core::op]
pub(crate) async fn as__runtime__register_provider__allocate(
rc_op_state: Rc<RefCell<OpState>>,
provider_rid: ResourceId, provider_name: String,
) -> Result<()> {
let get_symbol = |fn_name: &str| {
let op_state = rc_op_state.borrow_mut();
let resource = op_state
.resource_table
.get::<DynamicLibraryResource>(provider_rid)
.expect("valid dylib");
let symbols = &resource.symbols;
*symbols.get(fn_name).expect("valid symbol").clone()
};
let drop_symbol = get_symbol("drop");
let symbol = get_symbol("new");
let call_args: Vec<Arg> = Vec::new();
let provider_pointer = unsafe { symbol.cif.call::<*mut c_void>(symbol.ptr, &call_args) };
let mut op_state = rc_op_state.borrow_mut();
let client = op_state.borrow_mut::<AtmosphereState>();
if client.providers.get(&provider_rid).is_none() {
client.providers.insert(
provider_rid,
ProviderInner {
name: provider_name,
provider: provider_pointer,
drop_fn: drop_symbol,
},
);
}
Ok(())
}
#[deno_core::op]
pub(crate) fn op_get_env(key: String) -> Result<Option<String>> {
if key.is_empty() {
return Err(type_error("Key is an empty string."));
}
if key.contains(&['=', '\0'] as &[char]) {
return Err(type_error(format!(
"Key contains invalid characters: {key:?}"
)));
}
let r = match env::var(key) {
Err(env::VarError::NotPresent) => None,
v => Some(v?),
};
Ok(r)
}
pub(crate) fn op_decls() -> Vec<::deno_core::OpDecl> {
vec![
op_get_env::decl(),
as__client_new::decl(),
as__client_finished::decl(),
as__runtime__register_provider__allocate::decl(),
as__runtime__provider__execute::decl(),
]
}
pub(crate) struct AtmosphereClient(pub Client);
impl AtmosphereClient {
pub async fn new(backend_pointer: *mut c_void, passphrase: &[u8]) -> Result<Self> {
Ok(Self(Client::new(backend_pointer, passphrase).await?))
}
}
impl Resource for AtmosphereClient {}
#[derive(Default)]
pub struct AtmosphereState {
providers: ProviderList,
executed_resource: BTreeSet<Urn>,
rid: ResourceId,
}
impl Drop for AtmosphereState {
fn drop(&mut self) {
let drop_provider = |(_, inner): (_, &ProviderInner)| {
unsafe {
let call_args = vec![NativeValue {
pointer: inner.provider,
}
.as_arg(inner.drop_fn.parameter_types.get(0).unwrap())];
inner.drop_fn.cif.call::<()>(inner.drop_fn.ptr, &call_args);
};
};
self.providers.iter().for_each(drop_provider)
}
}
pub struct ParsedResourceUrn {
provider: String,
urn: Urn,
}
impl TryFrom<Urn> for ParsedResourceUrn {
type Error = AnyError;
fn try_from(urn: Urn) -> std::result::Result<Self, Self::Error> {
let nss: Vec<&str> = urn.nss().split(":").collect();
let provider = nss
.first()
.ok_or(anyhow!("invalid provider"))
.cloned()?
.to_string();
Ok(Self { provider, urn })
}
}