use super::CloudProvider;
use clap::{Args, ValueEnum};
use color_eyre::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteDeploymentPolicy {
pub providers: ProviderPreferences,
pub cost_limits: CostPolicy,
pub regions: RegionPolicy,
pub failover: FailoverPolicy,
pub serverless: ServerlessPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderPreferences {
pub gpu_providers: Vec<CloudProvider>,
pub cpu_intensive: Vec<CloudProvider>,
pub memory_intensive: Vec<CloudProvider>,
pub cost_optimized: Vec<CloudProvider>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostPolicy {
pub max_hourly_cost: Option<f32>,
pub prefer_spot: bool,
pub auto_terminate_after_hours: Option<u32>,
pub optimization_strategy: CostOptimization,
}
#[derive(Debug, Clone, Serialize, Deserialize, ValueEnum)]
pub enum CostOptimization {
Cheapest,
Balanced,
Performance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionPolicy {
pub preferred_regions: Vec<String>,
pub allow_fallback_regions: bool,
pub max_latency_ms: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailoverPolicy {
pub max_retries: u32,
pub retry_delay_seconds: u32,
pub retry_different_regions: bool,
pub retry_different_providers: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerlessPolicy {
pub enable: bool,
pub provider: FaasProvider,
pub default_memory_mb: u32,
pub default_timeout_secs: u32,
pub fallback_to_vm: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum FaasProvider {
AwsLambda {
#[serde(default = "default_aws_region")]
region: String,
},
GcpFunctions {
#[serde(default)]
project_id: String,
},
AzureFunctions {
#[serde(default)]
subscription_id: String,
},
Custom { endpoint: String },
}
fn default_aws_region() -> String {
"us-east-1".to_string()
}
#[derive(Debug, Clone, ValueEnum)]
pub enum FaasProviderType {
#[value(name = "aws-lambda")]
AwsLambda,
#[value(name = "gcp-functions")]
GcpFunctions,
#[value(name = "azure-functions")]
AzureFunctions,
#[value(name = "custom")]
Custom,
}
impl Default for RemoteDeploymentPolicy {
fn default() -> Self {
Self {
providers: ProviderPreferences {
gpu_providers: vec![
CloudProvider::RunPod,
CloudProvider::LambdaLabs,
CloudProvider::VastAi,
CloudProvider::PrimeIntellect,
CloudProvider::Crusoe,
CloudProvider::GCP,
CloudProvider::AWS,
],
cpu_intensive: vec![
CloudProvider::Hetzner,
CloudProvider::Vultr,
CloudProvider::DigitalOcean,
CloudProvider::AWS,
],
memory_intensive: vec![
CloudProvider::AWS,
CloudProvider::GCP,
CloudProvider::Hetzner,
],
cost_optimized: vec![
CloudProvider::VastAi,
CloudProvider::Hetzner,
CloudProvider::Vultr,
CloudProvider::DigitalOcean,
],
},
cost_limits: CostPolicy {
max_hourly_cost: Some(5.0),
prefer_spot: true,
auto_terminate_after_hours: Some(24),
optimization_strategy: CostOptimization::Balanced,
},
regions: RegionPolicy {
preferred_regions: vec!["us-east-1".to_string(), "us-west-2".to_string()],
allow_fallback_regions: true,
max_latency_ms: Some(100),
},
failover: FailoverPolicy {
max_retries: 3,
retry_delay_seconds: 30,
retry_different_regions: true,
retry_different_providers: true,
},
serverless: ServerlessPolicy {
enable: true,
provider: FaasProvider::AwsLambda {
region: "us-east-1".to_string(),
},
default_memory_mb: 512,
default_timeout_secs: 300,
fallback_to_vm: true,
},
}
}
}
impl RemoteDeploymentPolicy {
pub fn load() -> Result<Self> {
let path = Self::config_path()?;
if path.exists() {
let content = std::fs::read_to_string(&path)?;
serde_json::from_str(&content)
.or_else(|_| toml::from_str(&content))
.map_err(|e| color_eyre::eyre::eyre!("Failed to parse deployment policy: {}", e))
} else {
Ok(Self::default())
}
}
pub fn save(&self) -> Result<()> {
let path = Self::config_path()?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
std::fs::write(&path, content)?;
Ok(())
}
fn config_path() -> Result<PathBuf> {
let config_dir = dirs::config_dir()
.ok_or_else(|| color_eyre::eyre::eyre!("Could not find config directory"))?;
Ok(config_dir.join("tangle").join("deployment-policy.json"))
}
}
#[derive(Debug, Args)]
pub struct PolicyConfigureArgs {
#[arg(long)]
pub gpu_providers: Option<String>,
#[arg(long)]
pub cpu_providers: Option<String>,
#[arg(long)]
pub memory_providers: Option<String>,
#[arg(long)]
pub cost_providers: Option<String>,
#[arg(long)]
pub max_cost: Option<f32>,
#[arg(long)]
pub prefer_spot: Option<bool>,
#[arg(long)]
pub auto_terminate: Option<u32>,
#[arg(long)]
pub regions: Option<String>,
#[arg(long, value_enum)]
pub cost_strategy: Option<CostOptimization>,
#[arg(long)]
pub serverless: Option<bool>,
#[arg(long, value_enum)]
pub faas_provider: Option<FaasProviderType>,
#[arg(long)]
pub faas_aws_region: Option<String>,
#[arg(long)]
pub faas_gcp_project: Option<String>,
#[arg(long)]
pub faas_azure_subscription: Option<String>,
#[arg(long)]
pub faas_custom_endpoint: Option<String>,
#[arg(long)]
pub faas_memory: Option<u32>,
#[arg(long)]
pub faas_timeout: Option<u32>,
#[arg(long)]
pub serverless_fallback: Option<bool>,
}
pub async fn configure_policy(args: PolicyConfigureArgs) -> Result<()> {
println!("🔧 Configuring Remote Deployment Policy\n");
let mut policy = RemoteDeploymentPolicy::load()?;
let mut changed = false;
if let Some(providers) = args.gpu_providers {
policy.providers.gpu_providers = parse_providers(&providers)?;
println!("✓ GPU providers: {:?}", policy.providers.gpu_providers);
changed = true;
}
if let Some(providers) = args.cpu_providers {
policy.providers.cpu_intensive = parse_providers(&providers)?;
println!("✓ CPU providers: {:?}", policy.providers.cpu_intensive);
changed = true;
}
if let Some(providers) = args.memory_providers {
policy.providers.memory_intensive = parse_providers(&providers)?;
println!(
"✓ Memory providers: {:?}",
policy.providers.memory_intensive
);
changed = true;
}
if let Some(providers) = args.cost_providers {
policy.providers.cost_optimized = parse_providers(&providers)?;
println!("✓ Cost providers: {:?}", policy.providers.cost_optimized);
changed = true;
}
if let Some(max_cost) = args.max_cost {
policy.cost_limits.max_hourly_cost = Some(max_cost);
println!("✓ Max hourly cost: ${:.2}", max_cost);
changed = true;
}
if let Some(prefer_spot) = args.prefer_spot {
policy.cost_limits.prefer_spot = prefer_spot;
println!("✓ Prefer spot instances: {}", prefer_spot);
changed = true;
}
if let Some(auto_terminate) = args.auto_terminate {
policy.cost_limits.auto_terminate_after_hours = Some(auto_terminate);
println!("✓ Auto-terminate after: {}h", auto_terminate);
changed = true;
}
if let Some(regions) = args.regions {
policy.regions.preferred_regions =
regions.split(',').map(|s| s.trim().to_string()).collect();
println!(
"✓ Preferred regions: {:?}",
policy.regions.preferred_regions
);
changed = true;
}
if let Some(strategy) = args.cost_strategy {
println!("✓ Cost strategy: {:?}", strategy);
policy.cost_limits.optimization_strategy = strategy;
changed = true;
}
if let Some(serverless) = args.serverless {
policy.serverless.enable = serverless;
println!("✓ Serverless optimization: {}", serverless);
changed = true;
}
if let Some(provider_type) = args.faas_provider {
let provider = match provider_type {
FaasProviderType::AwsLambda => {
let region = args
.faas_aws_region
.unwrap_or_else(|| "us-east-1".to_string());
FaasProvider::AwsLambda { region }
}
FaasProviderType::GcpFunctions => {
let project_id = args.faas_gcp_project.ok_or_else(|| {
color_eyre::eyre::eyre!("GCP Functions requires --faas-gcp-project")
})?;
FaasProvider::GcpFunctions { project_id }
}
FaasProviderType::AzureFunctions => {
let subscription_id = args.faas_azure_subscription.ok_or_else(|| {
color_eyre::eyre::eyre!("Azure Functions requires --faas-azure-subscription")
})?;
FaasProvider::AzureFunctions { subscription_id }
}
FaasProviderType::Custom => {
let endpoint = args.faas_custom_endpoint.ok_or_else(|| {
color_eyre::eyre::eyre!("Custom FaaS requires --faas-custom-endpoint")
})?;
FaasProvider::Custom { endpoint }
}
};
println!("✓ FaaS provider: {:?}", provider);
policy.serverless.provider = provider;
changed = true;
}
if let Some(memory) = args.faas_memory {
policy.serverless.default_memory_mb = memory;
println!("✓ FaaS memory: {}MB", memory);
changed = true;
}
if let Some(timeout) = args.faas_timeout {
policy.serverless.default_timeout_secs = timeout;
println!("✓ FaaS timeout: {}s", timeout);
changed = true;
}
if let Some(fallback) = args.serverless_fallback {
policy.serverless.fallback_to_vm = fallback;
println!("✓ Serverless fallback to VM: {}", fallback);
changed = true;
}
if changed {
policy.save()?;
println!("\n✅ Deployment policy updated!");
println!(" Blueprint Manager will use these settings for remote deployments.");
} else {
println!("No changes specified. Current policy:");
show_current_policy(&policy).await?;
}
Ok(())
}
pub async fn show_policy() -> Result<()> {
println!("📋 Current Remote Deployment Policy\n");
let policy = RemoteDeploymentPolicy::load()?;
show_current_policy(&policy).await
}
async fn show_current_policy(policy: &RemoteDeploymentPolicy) -> Result<()> {
println!("Provider Preferences:");
println!(" GPU workloads: {:?}", policy.providers.gpu_providers);
println!(" CPU intensive: {:?}", policy.providers.cpu_intensive);
println!(
" Memory intensive: {:?}",
policy.providers.memory_intensive
);
println!(
" Cost optimized: {:?}",
policy.providers.cost_optimized
);
println!("\nCost Limits:");
if let Some(max_cost) = policy.cost_limits.max_hourly_cost {
println!(" Max hourly cost: ${:.2}", max_cost);
} else {
println!(" Max hourly cost: No limit");
}
println!(" Prefer spot: {}", policy.cost_limits.prefer_spot);
if let Some(ttl) = policy.cost_limits.auto_terminate_after_hours {
println!(" Auto-terminate: {}h", ttl);
}
println!(
" Strategy: {:?}",
policy.cost_limits.optimization_strategy
);
println!("\nRegional Preferences:");
println!(
" Preferred regions: {:?}",
policy.regions.preferred_regions
);
println!(
" Allow fallback: {}",
policy.regions.allow_fallback_regions
);
println!("\nFailover Settings:");
println!(" Max retries: {}", policy.failover.max_retries);
println!(
" Retry delay: {}s",
policy.failover.retry_delay_seconds
);
println!("\nServerless Settings:");
println!(" Enabled: {}", policy.serverless.enable);
match &policy.serverless.provider {
FaasProvider::AwsLambda { region } => {
println!(" FaaS provider: AWS Lambda ({})", region);
}
FaasProvider::GcpFunctions { project_id } => {
println!(" FaaS provider: GCP Functions ({})", project_id);
}
FaasProvider::AzureFunctions { subscription_id } => {
println!(
" FaaS provider: Azure Functions ({})",
subscription_id
);
}
FaasProvider::Custom { endpoint } => {
println!(" FaaS provider: Custom ({})", endpoint);
}
}
println!(
" Default memory: {}MB",
policy.serverless.default_memory_mb
);
println!(
" Default timeout: {}s",
policy.serverless.default_timeout_secs
);
println!(" Fallback to VM: {}", policy.serverless.fallback_to_vm);
Ok(())
}
fn parse_providers(input: &str) -> Result<Vec<CloudProvider>> {
input
.split(',')
.map(|s| {
let trimmed = s.trim().to_lowercase();
match trimmed.as_str() {
"aws" => Ok(CloudProvider::AWS),
"gcp" | "google" => Ok(CloudProvider::GCP),
"azure" => Ok(CloudProvider::Azure),
"digitalocean" | "do" => Ok(CloudProvider::DigitalOcean),
"vultr" => Ok(CloudProvider::Vultr),
"hetzner" => Ok(CloudProvider::Hetzner),
"runpod" => Ok(CloudProvider::RunPod),
"lambda" | "lambda-labs" | "lambdalabs" => Ok(CloudProvider::LambdaLabs),
"prime-intellect" | "primeintellect" | "pi" => Ok(CloudProvider::PrimeIntellect),
"vast" | "vast-ai" | "vastai" => Ok(CloudProvider::VastAi),
"crusoe" => Ok(CloudProvider::Crusoe),
_ => Err(color_eyre::eyre::eyre!("Unknown provider: {}", trimmed)),
}
})
.collect()
}