feat: Vendor pezkuwi-subxt and pezkuwi-zombienet-sdk into monorepo
- Add pezkuwi-subxt crates to vendor/pezkuwi-subxt - Add pezkuwi-zombienet-sdk crates to vendor/pezkuwi-zombienet-sdk - Convert git dependencies to path dependencies - Add vendor crates to workspace members - Remove test/example crates from vendor (not needed for SDK) - Fix feature propagation issues detected by zepter - Fix workspace inheritance for internal dependencies - All 606 crates now in workspace - All 6919 internal dependency links verified correct - No git dependencies remaining
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use support::fs::FileSystem;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{trace, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::node::{NativeNode, NativeNodeOptions};
|
||||
use crate::{
|
||||
constants::NAMESPACE_PREFIX,
|
||||
native::{node::DeserializableNativeNodeOptions, provider},
|
||||
shared::helpers::extract_execution_result,
|
||||
types::{
|
||||
GenerateFileCommand, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
|
||||
SpawnNodeOptions,
|
||||
},
|
||||
DynNode, NativeProvider, ProviderError, ProviderNamespace, ProviderNode,
|
||||
};
|
||||
|
||||
pub(super) struct NativeNamespace<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone,
|
||||
{
|
||||
weak: Weak<NativeNamespace<FS>>,
|
||||
name: String,
|
||||
provider: Weak<NativeProvider<FS>>,
|
||||
base_dir: PathBuf,
|
||||
capabilities: ProviderCapabilities,
|
||||
filesystem: FS,
|
||||
pub(super) nodes: RwLock<HashMap<String, Arc<NativeNode<FS>>>>,
|
||||
}
|
||||
|
||||
impl<FS> NativeNamespace<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(super) async fn new(
|
||||
provider: &Weak<NativeProvider<FS>>,
|
||||
tmp_dir: &PathBuf,
|
||||
capabilities: &ProviderCapabilities,
|
||||
filesystem: &FS,
|
||||
custom_base_dir: Option<&Path>,
|
||||
) -> Result<Arc<Self>, ProviderError> {
|
||||
let name = format!("{}{}", NAMESPACE_PREFIX, Uuid::new_v4());
|
||||
let base_dir = if let Some(custom_base_dir) = custom_base_dir {
|
||||
if !filesystem.exists(custom_base_dir).await {
|
||||
filesystem.create_dir_all(custom_base_dir).await?;
|
||||
} else {
|
||||
warn!(
|
||||
"⚠️ Using and existing directory {} as base dir",
|
||||
custom_base_dir.to_string_lossy()
|
||||
);
|
||||
}
|
||||
PathBuf::from(custom_base_dir)
|
||||
} else {
|
||||
let base_dir = PathBuf::from_iter([tmp_dir, &PathBuf::from(&name)]);
|
||||
filesystem.create_dir(&base_dir).await?;
|
||||
base_dir
|
||||
};
|
||||
|
||||
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
||||
weak: weak.clone(),
|
||||
provider: provider.clone(),
|
||||
name,
|
||||
base_dir,
|
||||
capabilities: capabilities.clone(),
|
||||
filesystem: filesystem.clone(),
|
||||
nodes: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn attach_to_live(
|
||||
provider: &Weak<NativeProvider<FS>>,
|
||||
capabilities: &ProviderCapabilities,
|
||||
filesystem: &FS,
|
||||
custom_base_dir: &Path,
|
||||
name: &str,
|
||||
) -> Result<Arc<Self>, ProviderError> {
|
||||
let base_dir = custom_base_dir.to_path_buf();
|
||||
|
||||
Ok(Arc::new_cyclic(|weak| NativeNamespace {
|
||||
weak: weak.clone(),
|
||||
provider: provider.clone(),
|
||||
name: name.to_string(),
|
||||
base_dir,
|
||||
capabilities: capabilities.clone(),
|
||||
filesystem: filesystem.clone(),
|
||||
nodes: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<FS> ProviderNamespace for NativeNamespace<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn base_dir(&self) -> &PathBuf {
|
||||
&self.base_dir
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> &ProviderCapabilities {
|
||||
&self.capabilities
|
||||
}
|
||||
|
||||
fn provider_name(&self) -> &str {
|
||||
provider::PROVIDER_NAME
|
||||
}
|
||||
|
||||
async fn nodes(&self) -> HashMap<String, DynNode> {
|
||||
self.nodes
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.map(|(name, node)| (name.clone(), node.clone() as DynNode))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_node_available_args(
|
||||
&self,
|
||||
(command, _image): (String, Option<String>),
|
||||
) -> Result<String, ProviderError> {
|
||||
let temp_node = self
|
||||
.spawn_node(
|
||||
&SpawnNodeOptions::new(format!("temp-{}", Uuid::new_v4()), "bash".to_string())
|
||||
.args(vec!["-c", "while :; do sleep 1; done"]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let available_args_output = temp_node
|
||||
.run_command(RunCommandOptions::new(command.clone()).args(vec!["--help"]))
|
||||
.await?
|
||||
.map_err(|(_exit, status)| {
|
||||
ProviderError::NodeAvailableArgsError("".to_string(), command, status)
|
||||
})?;
|
||||
|
||||
temp_node.destroy().await?;
|
||||
|
||||
Ok(available_args_output)
|
||||
}
|
||||
|
||||
async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError> {
|
||||
trace!("spawn node options {options:?}");
|
||||
|
||||
let node = NativeNode::new(NativeNodeOptions {
|
||||
namespace: &self.weak,
|
||||
namespace_base_dir: &self.base_dir,
|
||||
name: &options.name,
|
||||
program: &options.program,
|
||||
args: &options.args,
|
||||
env: &options.env,
|
||||
startup_files: &options.injected_files,
|
||||
created_paths: &options.created_paths,
|
||||
db_snapshot: options.db_snapshot.as_ref(),
|
||||
filesystem: &self.filesystem,
|
||||
node_log_path: options.node_log_path.as_ref(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.nodes
|
||||
.write()
|
||||
.await
|
||||
.insert(options.name.clone(), node.clone());
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
async fn spawn_node_from_json(
|
||||
&self,
|
||||
json_value: &serde_json::Value,
|
||||
) -> Result<DynNode, ProviderError> {
|
||||
let deserializable: DeserializableNativeNodeOptions =
|
||||
serde_json::from_value(json_value.clone())?;
|
||||
let options = NativeNodeOptions::from_deserializable(
|
||||
&deserializable,
|
||||
&self.weak,
|
||||
&self.base_dir,
|
||||
&self.filesystem,
|
||||
);
|
||||
|
||||
let pid = json_value
|
||||
.get("process_handle")
|
||||
.and_then(|v| v.as_i64())
|
||||
.ok_or_else(|| ProviderError::InvalidConfig("Missing pid field".to_string()))?
|
||||
as i32;
|
||||
let node = NativeNode::attach_to_live(options, pid).await?;
|
||||
|
||||
self.nodes
|
||||
.write()
|
||||
.await
|
||||
.insert(node.name().to_string(), node.clone());
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError> {
|
||||
let node_name = if let Some(name) = options.temp_name {
|
||||
name
|
||||
} else {
|
||||
format!("temp-{}", Uuid::new_v4())
|
||||
};
|
||||
|
||||
// we spawn a node doing nothing but looping so we can execute our commands
|
||||
let temp_node = self
|
||||
.spawn_node(
|
||||
&SpawnNodeOptions::new(node_name, "bash".to_string())
|
||||
.args(vec!["-c", "while :; do sleep 1; done"])
|
||||
.injected_files(options.injected_files),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for GenerateFileCommand {
|
||||
program,
|
||||
args,
|
||||
env,
|
||||
local_output_path,
|
||||
} in options.commands
|
||||
{
|
||||
trace!(
|
||||
"🏗 building file {:?} in path {} with command {} {}",
|
||||
local_output_path.as_os_str(),
|
||||
self.base_dir.to_string_lossy(),
|
||||
program,
|
||||
args.join(" ")
|
||||
);
|
||||
let local_output_full_path = format!(
|
||||
"{}{}{}",
|
||||
self.base_dir.to_string_lossy(),
|
||||
if local_output_path.starts_with("/") {
|
||||
""
|
||||
} else {
|
||||
"/"
|
||||
},
|
||||
local_output_path.to_string_lossy()
|
||||
);
|
||||
|
||||
let contents = extract_execution_result(
|
||||
&temp_node,
|
||||
RunCommandOptions { program, args, env },
|
||||
options.expected_path.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
self.filesystem
|
||||
.write(local_output_full_path, contents)
|
||||
.await
|
||||
.map_err(|err| ProviderError::FileGenerationFailed(err.into()))?;
|
||||
}
|
||||
|
||||
temp_node.destroy().await
|
||||
}
|
||||
|
||||
async fn static_setup(&self) -> Result<(), ProviderError> {
|
||||
// no static setup exists for native provider
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn destroy(&self) -> Result<(), ProviderError> {
|
||||
let mut names = vec![];
|
||||
|
||||
for node in self.nodes.read().await.values() {
|
||||
node.abort()
|
||||
.await
|
||||
.map_err(|err| ProviderError::DestroyNodeFailed(node.name().to_string(), err))?;
|
||||
names.push(node.name().to_string());
|
||||
}
|
||||
|
||||
let mut nodes = self.nodes.write().await;
|
||||
for name in names {
|
||||
nodes.remove(&name);
|
||||
}
|
||||
|
||||
if let Some(provider) = self.provider.upgrade() {
|
||||
provider.namespaces.write().await.remove(&self.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use support::fs::local::LocalFileSystem;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
types::{GenerateFileCommand, GenerateFilesOptions},
|
||||
NativeProvider, Provider,
|
||||
};
|
||||
|
||||
fn unique_temp_dir() -> PathBuf {
|
||||
let mut base = std::env::temp_dir();
|
||||
base.push(format!("znet_native_ns_test_{}", uuid::Uuid::new_v4()));
|
||||
base
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generate_files_uses_expected_path_when_provided() {
|
||||
let fs = LocalFileSystem;
|
||||
let provider = NativeProvider::new(fs.clone());
|
||||
let base_dir = unique_temp_dir();
|
||||
// Namespace builder will create directory if needed
|
||||
let ns = provider
|
||||
.create_namespace_with_base_dir(&base_dir)
|
||||
.await
|
||||
.expect("namespace should be created");
|
||||
|
||||
// Create a unique on-host path that the native node will write to
|
||||
let expected_path =
|
||||
std::env::temp_dir().join(format!("znet_expected_{}.json", uuid::Uuid::new_v4()));
|
||||
|
||||
// Command will write JSON into expected_path; stdout will be something else to ensure we don't read it
|
||||
let program = "bash".to_string();
|
||||
let script = format!(
|
||||
"echo -n '{{\"hello\":\"world\"}}' > {} && echo should_not_be_used",
|
||||
expected_path.to_string_lossy()
|
||||
);
|
||||
let args: Vec<String> = vec!["-lc".into(), script];
|
||||
|
||||
let out_name = PathBuf::from("result_expected.json");
|
||||
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
||||
let options = GenerateFilesOptions::new(vec![cmd], None, Some(expected_path.clone()));
|
||||
|
||||
ns.generate_files(options)
|
||||
.await
|
||||
.expect("generation should succeed");
|
||||
|
||||
// Read produced file from namespace base_dir
|
||||
let produced_path = base_dir.join(out_name);
|
||||
let produced = fs
|
||||
.read_to_string(&produced_path)
|
||||
.await
|
||||
.expect("should read produced file");
|
||||
assert_eq!(produced, "{\"hello\":\"world\"}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generate_files_uses_stdout_when_expected_path_absent() {
|
||||
let fs = LocalFileSystem;
|
||||
let provider = NativeProvider::new(fs.clone());
|
||||
let base_dir = unique_temp_dir();
|
||||
let ns = provider
|
||||
.create_namespace_with_base_dir(&base_dir)
|
||||
.await
|
||||
.expect("namespace should be created");
|
||||
|
||||
// Command prints to stdout only
|
||||
let program = "bash".to_string();
|
||||
let args: Vec<String> = vec!["-lc".into(), "echo -n 42".into()];
|
||||
|
||||
let out_name = PathBuf::from("result_stdout.txt");
|
||||
let cmd = GenerateFileCommand::new(program, out_name.clone()).args(args);
|
||||
let options = GenerateFilesOptions::new(vec![cmd], None, None);
|
||||
|
||||
ns.generate_files(options)
|
||||
.await
|
||||
.expect("generation should succeed");
|
||||
|
||||
let produced_path = base_dir.join(out_name);
|
||||
let produced = fs
|
||||
.read_to_string(&produced_path)
|
||||
.await
|
||||
.expect("should read produced file");
|
||||
assert_eq!(produced, "42");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use configuration::types::AssetLocation;
|
||||
use flate2::read::GzDecoder;
|
||||
use futures::future::try_join_all;
|
||||
use nix::{
|
||||
sys::signal::{kill, Signal},
|
||||
unistd::Pid,
|
||||
};
|
||||
use serde::{ser::Error, Deserialize, Serialize, Serializer};
|
||||
use sha2::Digest;
|
||||
use support::{constants::THIS_IS_A_BUG, fs::FileSystem};
|
||||
use tar::Archive;
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncRead, AsyncReadExt, BufReader},
|
||||
process::{Child, ChildStderr, ChildStdout, Command},
|
||||
sync::{
|
||||
mpsc::{self, Sender},
|
||||
RwLock,
|
||||
},
|
||||
task::JoinHandle,
|
||||
time::sleep,
|
||||
try_join,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use super::namespace::NativeNamespace;
|
||||
use crate::{
|
||||
constants::{NODE_CONFIG_DIR, NODE_DATA_DIR, NODE_RELAY_DATA_DIR, NODE_SCRIPTS_DIR},
|
||||
native,
|
||||
types::{ExecutionResult, RunCommandOptions, RunScriptOptions, TransferedFile},
|
||||
ProviderError, ProviderNamespace, ProviderNode,
|
||||
};
|
||||
|
||||
pub(super) struct NativeNodeOptions<'a, FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(super) namespace: &'a Weak<NativeNamespace<FS>>,
|
||||
pub(super) namespace_base_dir: &'a PathBuf,
|
||||
pub(super) name: &'a str,
|
||||
pub(super) program: &'a str,
|
||||
pub(super) args: &'a [String],
|
||||
pub(super) env: &'a [(String, String)],
|
||||
pub(super) startup_files: &'a [TransferedFile],
|
||||
pub(super) created_paths: &'a [PathBuf],
|
||||
pub(super) db_snapshot: Option<&'a AssetLocation>,
|
||||
pub(super) filesystem: &'a FS,
|
||||
pub(super) node_log_path: Option<&'a PathBuf>,
|
||||
}
|
||||
|
||||
impl<'a, FS> NativeNodeOptions<'a, FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(super) fn from_deserializable(
|
||||
deserializable: &'a DeserializableNativeNodeOptions,
|
||||
namespace: &'a Weak<NativeNamespace<FS>>,
|
||||
namespace_base_dir: &'a PathBuf,
|
||||
filesystem: &'a FS,
|
||||
) -> NativeNodeOptions<'a, FS> {
|
||||
NativeNodeOptions {
|
||||
namespace,
|
||||
namespace_base_dir,
|
||||
name: &deserializable.name,
|
||||
program: &deserializable.program,
|
||||
args: &deserializable.args,
|
||||
env: &deserializable.env,
|
||||
startup_files: &[],
|
||||
created_paths: &[],
|
||||
db_snapshot: None,
|
||||
filesystem,
|
||||
node_log_path: deserializable.node_log_path.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct DeserializableNativeNodeOptions {
|
||||
pub name: String,
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
pub env: Vec<(String, String)>,
|
||||
pub node_log_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
enum ProcessHandle {
|
||||
Spawned(Child, Pid),
|
||||
Attached(Pid),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct NativeNode<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone,
|
||||
{
|
||||
#[serde(skip)]
|
||||
namespace: Weak<NativeNamespace<FS>>,
|
||||
name: String,
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
base_dir: PathBuf,
|
||||
config_dir: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
relay_data_dir: PathBuf,
|
||||
scripts_dir: PathBuf,
|
||||
log_path: PathBuf,
|
||||
#[serde(serialize_with = "serialize_process_handle")]
|
||||
// using RwLock from std to serialize properly, generally using sync locks is ok in async code as long as they
|
||||
// are not held across await points
|
||||
process_handle: std::sync::RwLock<Option<ProcessHandle>>,
|
||||
#[serde(skip)]
|
||||
stdout_reading_task: RwLock<Option<JoinHandle<()>>>,
|
||||
#[serde(skip)]
|
||||
stderr_reading_task: RwLock<Option<JoinHandle<()>>>,
|
||||
#[serde(skip)]
|
||||
log_writing_task: RwLock<Option<JoinHandle<()>>>,
|
||||
#[serde(skip)]
|
||||
filesystem: FS,
|
||||
provider_tag: String,
|
||||
}
|
||||
|
||||
impl<FS> NativeNode<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(super) async fn new(
|
||||
options: NativeNodeOptions<'_, FS>,
|
||||
) -> Result<Arc<Self>, ProviderError> {
|
||||
let filesystem = options.filesystem.clone();
|
||||
|
||||
let base_dir =
|
||||
PathBuf::from_iter([options.namespace_base_dir, &PathBuf::from(options.name)]);
|
||||
trace!("creating base_dir {:?}", base_dir);
|
||||
options.filesystem.create_dir_all(&base_dir).await?;
|
||||
trace!("created base_dir {:?}", base_dir);
|
||||
|
||||
let base_dir_raw = base_dir.to_string_lossy();
|
||||
let config_dir = PathBuf::from(format!("{base_dir_raw}{NODE_CONFIG_DIR}"));
|
||||
let data_dir = PathBuf::from(format!("{base_dir_raw}{NODE_DATA_DIR}"));
|
||||
let relay_data_dir = PathBuf::from(format!("{base_dir_raw}{NODE_RELAY_DATA_DIR}"));
|
||||
let scripts_dir = PathBuf::from(format!("{base_dir_raw}{NODE_SCRIPTS_DIR}"));
|
||||
let log_path = options
|
||||
.node_log_path
|
||||
.cloned()
|
||||
.unwrap_or_else(|| base_dir.join(format!("{}.log", options.name)));
|
||||
|
||||
trace!("creating dirs {:?}", config_dir);
|
||||
try_join!(
|
||||
filesystem.create_dir_all(&config_dir),
|
||||
filesystem.create_dir_all(&data_dir),
|
||||
filesystem.create_dir_all(&relay_data_dir),
|
||||
filesystem.create_dir_all(&scripts_dir),
|
||||
)?;
|
||||
trace!("created!");
|
||||
|
||||
let node = Arc::new(NativeNode {
|
||||
namespace: options.namespace.clone(),
|
||||
name: options.name.to_string(),
|
||||
program: options.program.to_string(),
|
||||
args: options.args.to_vec(),
|
||||
env: options.env.to_vec(),
|
||||
base_dir,
|
||||
config_dir,
|
||||
data_dir,
|
||||
relay_data_dir,
|
||||
scripts_dir,
|
||||
log_path,
|
||||
process_handle: std::sync::RwLock::new(None),
|
||||
stdout_reading_task: RwLock::new(None),
|
||||
stderr_reading_task: RwLock::new(None),
|
||||
log_writing_task: RwLock::new(None),
|
||||
filesystem: filesystem.clone(),
|
||||
provider_tag: native::provider::PROVIDER_NAME.to_string(),
|
||||
});
|
||||
|
||||
node.initialize_startup_paths(options.created_paths).await?;
|
||||
node.initialize_startup_files(options.startup_files).await?;
|
||||
|
||||
if let Some(db_snap) = options.db_snapshot {
|
||||
node.initialize_db_snapshot(db_snap).await?;
|
||||
}
|
||||
|
||||
let (stdout, stderr) = node.initialize_process().await?;
|
||||
|
||||
node.initialize_log_writing(stdout, stderr).await;
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
pub(super) async fn attach_to_live(
|
||||
options: NativeNodeOptions<'_, FS>,
|
||||
pid: i32,
|
||||
) -> Result<Arc<Self>, ProviderError> {
|
||||
let filesystem = options.filesystem.clone();
|
||||
|
||||
let base_dir =
|
||||
PathBuf::from_iter([options.namespace_base_dir, &PathBuf::from(options.name)]);
|
||||
trace!("creating base_dir {:?}", base_dir);
|
||||
options.filesystem.create_dir_all(&base_dir).await?;
|
||||
trace!("created base_dir {:?}", base_dir);
|
||||
|
||||
let base_dir_raw = base_dir.to_string_lossy();
|
||||
let config_dir = PathBuf::from(format!("{base_dir_raw}{NODE_CONFIG_DIR}"));
|
||||
let data_dir = PathBuf::from(format!("{base_dir_raw}{NODE_DATA_DIR}"));
|
||||
let relay_data_dir = PathBuf::from(format!("{base_dir_raw}{NODE_RELAY_DATA_DIR}"));
|
||||
let scripts_dir = PathBuf::from(format!("{base_dir_raw}{NODE_SCRIPTS_DIR}"));
|
||||
let log_path = options
|
||||
.node_log_path
|
||||
.cloned()
|
||||
.unwrap_or_else(|| base_dir.join(format!("{}.log", options.name)));
|
||||
|
||||
let pid = Pid::from_raw(pid);
|
||||
|
||||
let node = Arc::new(NativeNode {
|
||||
namespace: options.namespace.clone(),
|
||||
name: options.name.to_string(),
|
||||
program: options.program.to_string(),
|
||||
args: options.args.to_vec(),
|
||||
env: options.env.to_vec(),
|
||||
base_dir,
|
||||
config_dir,
|
||||
data_dir,
|
||||
relay_data_dir,
|
||||
scripts_dir,
|
||||
log_path,
|
||||
process_handle: std::sync::RwLock::new(Some(ProcessHandle::Attached(pid))),
|
||||
stdout_reading_task: RwLock::new(None),
|
||||
stderr_reading_task: RwLock::new(None),
|
||||
log_writing_task: RwLock::new(None),
|
||||
filesystem: filesystem.clone(),
|
||||
provider_tag: native::provider::PROVIDER_NAME.to_string(),
|
||||
});
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
async fn initialize_startup_paths(&self, paths: &[PathBuf]) -> Result<(), ProviderError> {
|
||||
trace!("creating paths {:?}", paths);
|
||||
let base_dir_raw = self.base_dir.to_string_lossy();
|
||||
try_join_all(paths.iter().map(|file| {
|
||||
let full_path = format!("{base_dir_raw}{}", file.to_string_lossy());
|
||||
self.filesystem.create_dir_all(full_path)
|
||||
}))
|
||||
.await?;
|
||||
trace!("paths created!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialize_startup_files(
|
||||
&self,
|
||||
startup_files: &[TransferedFile],
|
||||
) -> Result<(), ProviderError> {
|
||||
trace!("creating files {:?}", startup_files);
|
||||
try_join_all(
|
||||
startup_files
|
||||
.iter()
|
||||
.map(|file| self.send_file(&file.local_path, &file.remote_path, &file.mode)),
|
||||
)
|
||||
.await?;
|
||||
trace!("files created!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialize_db_snapshot(
|
||||
&self,
|
||||
db_snapshot: &AssetLocation,
|
||||
) -> Result<(), ProviderError> {
|
||||
trace!("snap: {db_snapshot}");
|
||||
|
||||
// check if we need to get the db or is already in the ns
|
||||
let ns_base_dir = self.namespace_base_dir();
|
||||
let hashed_location = match db_snapshot {
|
||||
AssetLocation::Url(location) => hex::encode(sha2::Sha256::digest(location.to_string())),
|
||||
AssetLocation::FilePath(filepath) => {
|
||||
hex::encode(sha2::Sha256::digest(filepath.to_string_lossy().to_string()))
|
||||
},
|
||||
};
|
||||
|
||||
let full_path = format!("{ns_base_dir}/{hashed_location}.tgz");
|
||||
trace!("db_snap fullpath in ns: {full_path}");
|
||||
if !self.filesystem.exists(&full_path).await {
|
||||
// needs to download/copy
|
||||
self.get_db_snapshot(db_snapshot, &full_path).await?;
|
||||
}
|
||||
|
||||
let contents = self.filesystem.read(&full_path).await.unwrap();
|
||||
let gz = GzDecoder::new(&contents[..]);
|
||||
let mut archive = Archive::new(gz);
|
||||
archive
|
||||
.unpack(self.base_dir.to_string_lossy().as_ref())
|
||||
.unwrap();
|
||||
|
||||
if std::env::var("ZOMBIE_RM_TGZ_AFTER_EXTRACT").is_ok() {
|
||||
let res = fs::remove_file(&full_path).await;
|
||||
trace!("removing {}, result {:?}", full_path, res);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_db_snapshot(
|
||||
&self,
|
||||
location: &AssetLocation,
|
||||
full_path: &str,
|
||||
) -> Result<(), ProviderError> {
|
||||
trace!("getting db_snapshot from: {:?} to: {full_path}", location);
|
||||
match location {
|
||||
AssetLocation::Url(location) => {
|
||||
let res = reqwest::get(location.as_ref())
|
||||
.await
|
||||
.map_err(|err| ProviderError::DownloadFile(location.to_string(), err.into()))?;
|
||||
|
||||
let contents: &[u8] = &res.bytes().await.unwrap();
|
||||
trace!("writing: {full_path}");
|
||||
self.filesystem.write(full_path, contents).await?;
|
||||
},
|
||||
AssetLocation::FilePath(filepath) => {
|
||||
self.filesystem.copy(filepath, full_path).await?;
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialize_process(&self) -> Result<(ChildStdout, ChildStderr), ProviderError> {
|
||||
let filtered_env: HashMap<String, String> = env::vars()
|
||||
.filter(|(k, _)| k == "TZ" || k == "LANG" || k == "PATH")
|
||||
.collect();
|
||||
|
||||
let mut process = Command::new(&self.program)
|
||||
.args(&self.args)
|
||||
.env_clear()
|
||||
.envs(&filtered_env) // minimal environment
|
||||
.envs(self.env.to_vec())
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.current_dir(&self.base_dir)
|
||||
.spawn()
|
||||
.map_err(|err| ProviderError::NodeSpawningFailed(self.name.to_string(), err.into()))?;
|
||||
let stdout = process
|
||||
.stdout
|
||||
.take()
|
||||
.expect(&format!("infaillible, stdout is piped {THIS_IS_A_BUG}"));
|
||||
let stderr = process
|
||||
.stderr
|
||||
.take()
|
||||
.expect(&format!("infaillible, stderr is piped {THIS_IS_A_BUG}"));
|
||||
|
||||
let pid = Pid::from_raw(
|
||||
process
|
||||
.id()
|
||||
.ok_or_else(|| ProviderError::ProcessIdRetrievalFailed(self.name.to_string()))?
|
||||
as i32,
|
||||
);
|
||||
self.process_handle
|
||||
.write()
|
||||
.map_err(|_e| ProviderError::FailedToAcquireLock(self.name.clone()))?
|
||||
.replace(ProcessHandle::Spawned(process, pid));
|
||||
|
||||
Ok((stdout, stderr))
|
||||
}
|
||||
|
||||
async fn initialize_log_writing(&self, stdout: ChildStdout, stderr: ChildStderr) {
|
||||
let (stdout_tx, mut rx) = mpsc::channel(10);
|
||||
let stderr_tx = stdout_tx.clone();
|
||||
|
||||
self.stdout_reading_task
|
||||
.write()
|
||||
.await
|
||||
.replace(self.create_stream_polling_task(stdout, stdout_tx));
|
||||
self.stderr_reading_task
|
||||
.write()
|
||||
.await
|
||||
.replace(self.create_stream_polling_task(stderr, stderr_tx));
|
||||
|
||||
let filesystem = self.filesystem.clone();
|
||||
let log_path = self.log_path.clone();
|
||||
|
||||
self.log_writing_task
|
||||
.write()
|
||||
.await
|
||||
.replace(tokio::spawn(async move {
|
||||
loop {
|
||||
while let Some(Ok(data)) = rx.recv().await {
|
||||
// TODO: find a better way instead of ignoring error ?
|
||||
let _ = filesystem.append(&log_path, data).await;
|
||||
}
|
||||
sleep(Duration::from_millis(250)).await;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
fn create_stream_polling_task(
|
||||
&self,
|
||||
stream: impl AsyncRead + Unpin + Send + 'static,
|
||||
tx: Sender<Result<Vec<u8>, std::io::Error>>,
|
||||
) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut buffer = vec![0u8; 1024];
|
||||
|
||||
loop {
|
||||
match reader.read(&mut buffer).await {
|
||||
Ok(0) => {
|
||||
let _ = tx.send(Ok(Vec::new())).await;
|
||||
break;
|
||||
},
|
||||
Ok(n) => {
|
||||
let _ = tx.send(Ok(buffer[..n].to_vec())).await;
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(e)).await;
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn process_id(&self) -> Result<Pid, ProviderError> {
|
||||
let pid = self
|
||||
.process_handle
|
||||
.read()
|
||||
.map_err(|_e| ProviderError::FailedToAcquireLock(self.name.clone()))?
|
||||
.as_ref()
|
||||
.map(|handle| match handle {
|
||||
ProcessHandle::Spawned(_, pid) => *pid,
|
||||
ProcessHandle::Attached(pid) => *pid,
|
||||
})
|
||||
.ok_or_else(|| ProviderError::ProcessIdRetrievalFailed(self.name.to_string()))?;
|
||||
|
||||
Ok(pid)
|
||||
}
|
||||
|
||||
pub(crate) async fn abort(&self) -> anyhow::Result<()> {
|
||||
if let Some(task) = self.log_writing_task.write().await.take() {
|
||||
task.abort();
|
||||
}
|
||||
if let Some(task) = self.stdout_reading_task.write().await.take() {
|
||||
task.abort();
|
||||
}
|
||||
if let Some(task) = self.stderr_reading_task.write().await.take() {
|
||||
task.abort();
|
||||
}
|
||||
|
||||
let process_handle = {
|
||||
let mut guard = self
|
||||
.process_handle
|
||||
.write()
|
||||
.map_err(|_e| ProviderError::FailedToAcquireLock(self.name.clone()))?;
|
||||
guard
|
||||
.take()
|
||||
.ok_or_else(|| anyhow!("no process was attached for the node"))?
|
||||
};
|
||||
|
||||
match process_handle {
|
||||
ProcessHandle::Spawned(mut child, _pid) => {
|
||||
child.kill().await?;
|
||||
},
|
||||
ProcessHandle::Attached(pid) => {
|
||||
kill(pid, Signal::SIGKILL)
|
||||
.map_err(|err| anyhow!("Failed to kill attached process {pid}: {err}"))?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn namespace_base_dir(&self) -> String {
|
||||
self.namespace
|
||||
.upgrade()
|
||||
.map(|namespace| namespace.base_dir().to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| panic!("namespace shouldn't be dropped, {THIS_IS_A_BUG}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<FS> ProviderNode for NativeNode<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<&str> {
|
||||
self.args.iter().map(|arg| arg.as_str()).collect()
|
||||
}
|
||||
|
||||
fn base_dir(&self) -> &PathBuf {
|
||||
&self.base_dir
|
||||
}
|
||||
|
||||
fn config_dir(&self) -> &PathBuf {
|
||||
&self.config_dir
|
||||
}
|
||||
|
||||
fn data_dir(&self) -> &PathBuf {
|
||||
&self.data_dir
|
||||
}
|
||||
|
||||
fn relay_data_dir(&self) -> &PathBuf {
|
||||
&self.relay_data_dir
|
||||
}
|
||||
|
||||
fn scripts_dir(&self) -> &PathBuf {
|
||||
&self.scripts_dir
|
||||
}
|
||||
|
||||
fn log_path(&self) -> &PathBuf {
|
||||
&self.log_path
|
||||
}
|
||||
|
||||
fn log_cmd(&self) -> String {
|
||||
format!("tail -f {}", self.log_path().to_string_lossy())
|
||||
}
|
||||
|
||||
fn path_in_node(&self, file: &Path) -> PathBuf {
|
||||
let full_path = format!(
|
||||
"{}/{}",
|
||||
self.base_dir.to_string_lossy(),
|
||||
file.to_string_lossy()
|
||||
);
|
||||
PathBuf::from(full_path)
|
||||
}
|
||||
|
||||
async fn logs(&self) -> Result<String, ProviderError> {
|
||||
Ok(self.filesystem.read_to_string(&self.log_path).await?)
|
||||
}
|
||||
|
||||
async fn dump_logs(&self, local_dest: PathBuf) -> Result<(), ProviderError> {
|
||||
Ok(self.filesystem.copy(&self.log_path, local_dest).await?)
|
||||
}
|
||||
|
||||
async fn run_command(
|
||||
&self,
|
||||
options: RunCommandOptions,
|
||||
) -> Result<ExecutionResult, ProviderError> {
|
||||
let result = Command::new(options.program.clone())
|
||||
.args(options.args.clone())
|
||||
.envs(options.env)
|
||||
.current_dir(&self.base_dir)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
ProviderError::RunCommandError(
|
||||
format!("{} {}", &options.program, &options.args.join(" ")),
|
||||
"locally".to_string(),
|
||||
err.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if result.status.success() {
|
||||
Ok(Ok(String::from_utf8_lossy(&result.stdout).to_string()))
|
||||
} else {
|
||||
Ok(Err((
|
||||
result.status,
|
||||
String::from_utf8_lossy(&result.stderr).to_string(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_script(
|
||||
&self,
|
||||
options: RunScriptOptions,
|
||||
) -> Result<ExecutionResult, ProviderError> {
|
||||
let local_script_path = PathBuf::from(&options.local_script_path);
|
||||
|
||||
if !self.filesystem.exists(&local_script_path).await {
|
||||
return Err(ProviderError::ScriptNotFound(local_script_path));
|
||||
}
|
||||
|
||||
// extract file name and build remote file path
|
||||
let script_file_name = local_script_path
|
||||
.file_name()
|
||||
.map(|file_name| file_name.to_string_lossy().to_string())
|
||||
.ok_or(ProviderError::InvalidScriptPath(anyhow!(
|
||||
"Can't retrieve filename from script with path: {:?}",
|
||||
options.local_script_path
|
||||
)))?;
|
||||
let remote_script_path = format!(
|
||||
"{}/{}",
|
||||
self.scripts_dir.to_string_lossy(),
|
||||
script_file_name
|
||||
);
|
||||
|
||||
// copy and set script's execute permission
|
||||
self.filesystem
|
||||
.copy(local_script_path, &remote_script_path)
|
||||
.await?;
|
||||
self.filesystem.set_mode(&remote_script_path, 0o744).await?;
|
||||
|
||||
// execute script
|
||||
self.run_command(RunCommandOptions {
|
||||
program: remote_script_path,
|
||||
args: options.args,
|
||||
env: options.env,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_file(
|
||||
&self,
|
||||
local_file_path: &Path,
|
||||
remote_file_path: &Path,
|
||||
mode: &str,
|
||||
) -> Result<(), ProviderError> {
|
||||
let namespaced_remote_file_path = PathBuf::from(format!(
|
||||
"{}{}",
|
||||
&self.base_dir.to_string_lossy(),
|
||||
remote_file_path.to_string_lossy()
|
||||
));
|
||||
|
||||
self.filesystem
|
||||
.copy(local_file_path, &namespaced_remote_file_path)
|
||||
.await?;
|
||||
|
||||
self.run_command(
|
||||
RunCommandOptions::new("chmod")
|
||||
.args(vec![mode, &namespaced_remote_file_path.to_string_lossy()]),
|
||||
)
|
||||
.await?
|
||||
.map_err(|(_, err)| {
|
||||
ProviderError::SendFile(
|
||||
self.name.clone(),
|
||||
local_file_path.to_string_lossy().to_string(),
|
||||
anyhow!("{err}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_file(
|
||||
&self,
|
||||
remote_file_path: &Path,
|
||||
local_file_path: &Path,
|
||||
) -> Result<(), ProviderError> {
|
||||
let namespaced_remote_file_path = PathBuf::from(format!(
|
||||
"{}{}",
|
||||
&self.base_dir.to_string_lossy(),
|
||||
remote_file_path.to_string_lossy()
|
||||
));
|
||||
|
||||
self.filesystem
|
||||
.copy(namespaced_remote_file_path, local_file_path)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn pause(&self) -> Result<(), ProviderError> {
|
||||
let process_id = self.process_id()?;
|
||||
|
||||
kill(process_id, Signal::SIGSTOP)
|
||||
.map_err(|err| ProviderError::PauseNodeFailed(self.name.clone(), err.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resume(&self) -> Result<(), ProviderError> {
|
||||
let process_id = self.process_id()?;
|
||||
|
||||
nix::sys::signal::kill(process_id, Signal::SIGCONT)
|
||||
.map_err(|err| ProviderError::ResumeNodeFailed(self.name.clone(), err.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn restart(&self, after: Option<Duration>) -> Result<(), ProviderError> {
|
||||
if let Some(duration) = after {
|
||||
sleep(duration).await;
|
||||
}
|
||||
|
||||
self.abort()
|
||||
.await
|
||||
.map_err(|err| ProviderError::RestartNodeFailed(self.name.clone(), err))?;
|
||||
|
||||
let (stdout, stderr) = self
|
||||
.initialize_process()
|
||||
.await
|
||||
.map_err(|err| ProviderError::RestartNodeFailed(self.name.clone(), err.into()))?;
|
||||
|
||||
self.initialize_log_writing(stdout, stderr).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn destroy(&self) -> Result<(), ProviderError> {
|
||||
self.abort()
|
||||
.await
|
||||
.map_err(|err| ProviderError::DestroyNodeFailed(self.name.clone(), err))?;
|
||||
|
||||
if let Some(namespace) = self.namespace.upgrade() {
|
||||
namespace.nodes.write().await.remove(&self.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_process_handle<S>(
|
||||
process_handle: &std::sync::RwLock<Option<ProcessHandle>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let pid = process_handle
|
||||
.read()
|
||||
.map_err(|_e| S::Error::custom("failed to acquire read lock"))?
|
||||
.as_ref()
|
||||
.map(|handle| match handle {
|
||||
ProcessHandle::Spawned(_, pid) => pid.as_raw(),
|
||||
ProcessHandle::Attached(pid) => pid.as_raw(),
|
||||
});
|
||||
pid.serialize(serializer)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use support::fs::FileSystem;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::namespace::NativeNamespace;
|
||||
use crate::{
|
||||
shared::helpers::extract_namespace_info, types::ProviderCapabilities, DynNamespace, Provider,
|
||||
ProviderError, ProviderNamespace,
|
||||
};
|
||||
|
||||
pub const PROVIDER_NAME: &str = "native";
|
||||
|
||||
pub struct NativeProvider<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone,
|
||||
{
|
||||
weak: Weak<NativeProvider<FS>>,
|
||||
capabilities: ProviderCapabilities,
|
||||
tmp_dir: PathBuf,
|
||||
filesystem: FS,
|
||||
pub(super) namespaces: RwLock<HashMap<String, Arc<NativeNamespace<FS>>>>,
|
||||
}
|
||||
|
||||
impl<FS> NativeProvider<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone,
|
||||
{
|
||||
pub fn new(filesystem: FS) -> Arc<Self> {
|
||||
Arc::new_cyclic(|weak| NativeProvider {
|
||||
weak: weak.clone(),
|
||||
capabilities: ProviderCapabilities {
|
||||
has_resources: false,
|
||||
requires_image: false,
|
||||
prefix_with_full_path: true,
|
||||
use_default_ports_in_cmd: false,
|
||||
},
|
||||
// NOTE: temp_dir in linux return `/tmp` but on mac something like
|
||||
// `/var/folders/rz/1cyx7hfj31qgb98d8_cg7jwh0000gn/T/`, having
|
||||
// one `trailing slash` and the other no can cause issues if
|
||||
// you try to build a fullpath by concatenate. Use Pathbuf to prevent the issue.
|
||||
tmp_dir: std::env::temp_dir(),
|
||||
filesystem,
|
||||
namespaces: RwLock::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tmp_dir(mut self, tmp_dir: impl Into<PathBuf>) -> Self {
|
||||
self.tmp_dir = tmp_dir.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<FS> Provider for NativeProvider<FS>
|
||||
where
|
||||
FS: FileSystem + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn name(&self) -> &str {
|
||||
PROVIDER_NAME
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> &ProviderCapabilities {
|
||||
&self.capabilities
|
||||
}
|
||||
|
||||
async fn namespaces(&self) -> HashMap<String, DynNamespace> {
|
||||
self.namespaces
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.map(|(name, namespace)| (name.clone(), namespace.clone() as DynNamespace))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn create_namespace(&self) -> Result<DynNamespace, ProviderError> {
|
||||
let namespace = NativeNamespace::new(
|
||||
&self.weak,
|
||||
&self.tmp_dir,
|
||||
&self.capabilities,
|
||||
&self.filesystem,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.namespaces
|
||||
.write()
|
||||
.await
|
||||
.insert(namespace.name().to_string(), namespace.clone());
|
||||
|
||||
Ok(namespace)
|
||||
}
|
||||
|
||||
async fn create_namespace_with_base_dir(
|
||||
&self,
|
||||
base_dir: &Path,
|
||||
) -> Result<DynNamespace, ProviderError> {
|
||||
let namespace = NativeNamespace::new(
|
||||
&self.weak,
|
||||
&self.tmp_dir,
|
||||
&self.capabilities,
|
||||
&self.filesystem,
|
||||
Some(base_dir),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.namespaces
|
||||
.write()
|
||||
.await
|
||||
.insert(namespace.name().to_string(), namespace.clone());
|
||||
|
||||
Ok(namespace)
|
||||
}
|
||||
|
||||
async fn create_namespace_from_json(
|
||||
&self,
|
||||
json_value: &serde_json::Value,
|
||||
) -> Result<DynNamespace, ProviderError> {
|
||||
let (base_dir, name) = extract_namespace_info(json_value)?;
|
||||
|
||||
let namespace = NativeNamespace::attach_to_live(
|
||||
&self.weak,
|
||||
&self.capabilities,
|
||||
&self.filesystem,
|
||||
&base_dir,
|
||||
&name,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.namespaces
|
||||
.write()
|
||||
.await
|
||||
.insert(namespace.name().to_string(), namespace.clone());
|
||||
|
||||
Ok(namespace)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user