blueprint-remote-providers 0.2.0-alpha.2

Remote service providers for Tangle Blueprints
Documentation
//! Common types and traits for all cloud providers

pub mod gpu_adapter;

use crate::core::remote::CloudProvider;
use async_trait::async_trait;
use blueprint_std::collections::HashMap;
use serde::{Deserialize, Serialize};

/// Result of instance type selection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstanceSelection {
    pub instance_type: String,
    pub spot_capable: bool,
    pub estimated_hourly_cost: Option<f64>,
}

/// Configuration for infrastructure provisioning
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvisioningConfig {
    /// Deployment name/identifier
    pub name: String,
    /// Target region
    pub region: String,
    /// SSH key name (provider-specific)
    pub ssh_key_name: Option<String>,
    /// AMI ID for AWS (optional)
    pub ami_id: Option<String>,
    /// Machine image for GCP (optional)
    pub machine_image: Option<String>,
    /// Additional provider-specific configuration
    pub custom_config: HashMap<String, String>,
}

impl Default for ProvisioningConfig {
    fn default() -> Self {
        Self {
            name: "blueprint-deployment".to_string(),
            region: "us-west-2".to_string(),
            ssh_key_name: None,
            ami_id: None,
            machine_image: None,
            custom_config: HashMap::new(),
        }
    }
}

/// Provisioned infrastructure details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvisionedInfrastructure {
    pub provider: CloudProvider,
    pub instance_id: String,
    pub public_ip: Option<String>,
    pub private_ip: Option<String>,
    pub region: String,
    pub instance_type: String,
    pub metadata: HashMap<String, String>,
}

impl ProvisionedInfrastructure {
    /// Check if the infrastructure is ready for deployment
    pub async fn is_ready(&self) -> bool {
        // Check if we have network connectivity
        let has_network = self.public_ip.is_some() || self.private_ip.is_some();
        if !has_network {
            return false;
        }

        // Perform health check based on endpoint availability
        if let Some(endpoint) = self.get_endpoint() {
            // Try SSH port (22) for VM instances
            self.check_port_open(&endpoint, 22).await
        } else {
            false
        }
    }

    /// Check if a port is open on the given host
    async fn check_port_open(&self, host: &str, port: u16) -> bool {
        use blueprint_std::time::Duration;
        use tokio::net::TcpStream;
        use tokio::time::timeout;

        let addr = format!("{host}:{port}");
        matches!(
            timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await,
            Ok(Ok(_))
        )
    }

    /// Get connection endpoint for this infrastructure
    pub fn get_endpoint(&self) -> Option<String> {
        self.public_ip.clone().or_else(|| self.private_ip.clone())
    }

    pub fn into_provisioned_instance(self) -> crate::infra::types::ProvisionedInstance {
        crate::infra::types::ProvisionedInstance {
            id: self.instance_id,
            provider: self.provider,
            instance_type: self.instance_type,
            region: self.region,
            public_ip: self.public_ip,
            private_ip: self.private_ip,
            status: crate::infra::types::InstanceStatus::Running,
        }
    }
}

/// Trait for cloud provider provisioners
#[async_trait]
pub trait CloudProvisioner: Send + Sync {
    type Config: Clone + Send + Sync;
    type Instance: Clone + Send + Sync;

    async fn new(config: Self::Config) -> crate::core::error::Result<Self>
    where
        Self: Sized;

    async fn provision_instance(
        &self,
        spec: &crate::core::resources::ResourceSpec,
        config: &ProvisioningConfig,
    ) -> crate::core::error::Result<ProvisionedInfrastructure>;

    async fn terminate_instance(&self, instance_id: &str) -> crate::core::error::Result<()>;
}