use crate::error::Result;
use blueprint_auth::proxy::DEFAULT_AUTH_PROXY_PORT;
use blueprint_core::info;
use clap::{Args, Parser};
use std::fmt::Display;
use std::net::{IpAddr, Ipv4Addr};
use std::path::{Path, PathBuf};
mod ctx;
pub use ctx::*;
#[cfg(feature = "vm-sandbox")]
pub static DEFAULT_ADDRESS_POOL: std::sync::LazyLock<ipnet::Ipv4Net> =
std::sync::LazyLock::new(|| "172.30.0.0/16".parse().unwrap());
#[derive(Debug, Parser)]
#[command(
name = "Blueprint Manager",
about = "A program executor that connects to the Tangle network and runs blueprints dynamically on the fly"
)]
pub struct BlueprintManagerCli {
#[command(flatten)]
pub config: BlueprintManagerConfig,
}
#[derive(Debug, Args, Default)]
pub struct BlueprintManagerConfig {
#[command(flatten)]
pub paths: Paths,
#[arg(long, short = 'v', action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(long)]
pub pretty: bool,
#[arg(long, alias = "id")]
pub instance_id: Option<String>,
#[arg(long, short = 't')]
pub test_mode: bool,
#[arg(long)]
pub allow_unchecked_attestations: bool,
#[arg(long, short = 's', default_value_t)]
pub preferred_source: SourceType,
#[cfg(feature = "vm-sandbox")]
#[command(flatten)]
pub vm_sandbox_options: VmSandboxOptions,
#[cfg(feature = "containers")]
#[command(flatten)]
pub container_options: ContainerOptions,
#[command(flatten)]
pub auth_proxy_opts: AuthProxyOpts,
#[cfg(feature = "remote-providers")]
#[command(flatten)]
pub remote_deployment_opts: RemoteDeploymentOptions,
}
impl BlueprintManagerConfig {
#[inline]
#[must_use]
pub fn blueprint_config_path(&self) -> Option<&Path> {
self.paths.blueprint_config.as_deref()
}
#[inline]
#[must_use]
pub fn keystore_uri(&self) -> &str {
&self.paths.keystore_uri
}
#[inline]
#[must_use]
pub fn data_dir(&self) -> &Path {
&self.paths.data_dir
}
#[inline]
#[must_use]
pub fn cache_dir(&self) -> &Path {
&self.paths.cache_dir
}
#[inline]
#[must_use]
pub fn runtime_dir(&self) -> &Path {
&self.paths.runtime_dir
}
#[inline]
#[must_use]
#[cfg(feature = "containers")]
pub fn kube_service_port(&self) -> u16 {
self.container_options.kube_service_port
}
}
#[derive(Args, Debug, Clone)]
pub struct Paths {
#[arg(short = 'c', long)]
pub blueprint_config: Option<PathBuf>,
#[arg(short = 'k', long, default_value = "./keystore")]
pub keystore_uri: String,
#[arg(long, short = 'd', default_value = "./data")]
pub data_dir: PathBuf,
#[arg(long, short = 'z', default_value_os_t = default_cache_dir())]
pub cache_dir: PathBuf,
#[arg(long, short, default_value_os_t = default_runtime_dir())]
pub runtime_dir: PathBuf,
}
impl Default for Paths {
fn default() -> Self {
Self {
blueprint_config: None,
keystore_uri: "./keystore".into(),
data_dir: PathBuf::from("./data"),
cache_dir: default_cache_dir(),
runtime_dir: default_runtime_dir(),
}
}
}
#[cfg(feature = "vm-sandbox")]
#[derive(Args, Debug, Clone)]
pub struct VmSandboxOptions {
#[arg(long)]
pub no_vm: bool,
#[arg(long, default_value_t = *DEFAULT_ADDRESS_POOL)]
pub default_address_pool: ipnet::Ipv4Net,
#[arg(long)]
pub network_interface: Option<String>,
}
#[cfg(feature = "vm-sandbox")]
impl Default for VmSandboxOptions {
fn default() -> Self {
Self {
no_vm: false,
default_address_pool: *DEFAULT_ADDRESS_POOL,
network_interface: None,
}
}
}
#[cfg(feature = "containers")]
#[derive(Args, Debug, Clone, Default)]
pub struct ContainerOptions {
#[arg(long, default_value_t = 0)]
pub kube_service_port: u16,
}
#[derive(Debug, Parser, Clone)]
pub struct AuthProxyOpts {
#[arg(long, default_value = "0.0.0.0")]
pub auth_proxy_host: IpAddr,
#[arg(long, default_value_t = DEFAULT_AUTH_PROXY_PORT)]
pub auth_proxy_port: u16,
}
impl Default for AuthProxyOpts {
fn default() -> Self {
Self {
auth_proxy_host: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
auth_proxy_port: DEFAULT_AUTH_PROXY_PORT,
}
}
}
#[cfg(feature = "remote-providers")]
#[derive(Debug, Parser, Clone)]
pub struct RemoteDeploymentOptions {
#[arg(long)]
pub enable_remote_deployments: bool,
#[arg(long)]
pub auto_select_cheapest: bool,
#[arg(long)]
pub preferred_provider: Option<String>,
#[arg(long, default_value_t = 1.0)]
pub max_hourly_cost: f64,
#[arg(long)]
pub cloud_credentials_path: Option<PathBuf>,
#[arg(long)]
pub prefer_kubernetes: bool,
}
#[cfg(feature = "remote-providers")]
impl Default for RemoteDeploymentOptions {
fn default() -> Self {
Self {
enable_remote_deployments: false,
auto_select_cheapest: true,
preferred_provider: None,
max_hourly_cost: 1.0,
cloud_credentials_path: None,
prefer_kubernetes: false,
}
}
}
impl BlueprintManagerConfig {
fn verify_directories_exist(&self) -> Result<()> {
if !self.cache_dir().exists() {
info!(
"Cache directory does not exist, creating it at `{}`",
self.cache_dir().display()
);
std::fs::create_dir_all(self.cache_dir())?;
}
if !self.runtime_dir().exists() {
info!(
"Runtime directory does not exist, creating it at `{}`",
self.runtime_dir().display()
);
std::fs::create_dir_all(self.runtime_dir())?;
}
if !self.data_dir().exists() {
info!(
"Data directory does not exist, creating it at `{}`",
self.data_dir().display()
);
std::fs::create_dir_all(self.data_dir())?;
}
let keystore = Path::new(self.keystore_uri());
if !keystore.exists() {
info!(
"Keystore directory does not exist, creating it at `{}`",
keystore.display()
);
std::fs::create_dir_all(keystore)?;
}
Ok(())
}
#[cfg(feature = "vm-sandbox")]
fn verify_network_interface(&mut self) -> Result<String> {
if let Some(interface) = self.vm_sandbox_options.network_interface.clone() {
return Ok(interface);
}
let Ok(interface) = netdev::interface::get_default_interface().map(|i| i.name) else {
blueprint_core::error!(
"Unable to determine the default network interface, you must specify it manually with --network-interface"
);
return Err(crate::error::Error::Other(String::from(
"Failed to determine default network interface",
)));
};
self.vm_sandbox_options.network_interface = Some(interface.clone());
Ok(interface)
}
}
fn default_cache_dir() -> PathBuf {
match dirs::cache_dir() {
Some(dir) => dir.join("blueprint-manager"),
None => PathBuf::from("./blueprint-manager/cache"),
}
}
fn default_runtime_dir() -> PathBuf {
match dirs::runtime_dir() {
Some(dir) => dir.join("blueprint-manager"),
None => PathBuf::from("./blueprint-manager/run"),
}
}
#[derive(clap::ValueEnum, Debug, Copy, Clone, Default, PartialEq, Eq)]
pub enum SourceType {
Container,
#[default]
Native,
Wasm,
}
impl Display for SourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceType::Container => write!(f, "container"),
SourceType::Native => write!(f, "native"),
SourceType::Wasm => write!(f, "wasm"),
}
}
}