use crate::core::error::{Error, Result};
use crate::core::resources::ResourceSpec;
use crate::infra::traits::{BlueprintDeploymentResult, CloudProviderAdapter};
use crate::infra::types::{InstanceStatus, ProvisionedInstance};
use crate::providers::aws::provisioner::AwsProvisioner;
use crate::providers::common::{ProvisionedInfrastructure, ProvisioningConfig};
use async_trait::async_trait;
use blueprint_core::{debug, info, warn};
use blueprint_std::collections::HashMap;
pub struct AwsAdapter {
provisioner: AwsProvisioner,
security_group_id: Option<String>,
key_pair_name: String,
}
impl AwsAdapter {
pub async fn new() -> Result<Self> {
let provisioner = AwsProvisioner::new().await?;
let key_pair_name = std::env::var("AWS_KEY_PAIR_NAME")
.unwrap_or_else(|_| "blueprint-remote-providers".to_string());
Ok(Self {
provisioner,
security_group_id: None, key_pair_name,
})
}
fn to_provisioned_instance(infra: ProvisionedInfrastructure) -> ProvisionedInstance {
ProvisionedInstance {
id: infra.instance_id,
public_ip: infra.public_ip,
private_ip: infra.private_ip,
status: crate::infra::types::InstanceStatus::Running,
provider: infra.provider,
region: infra.region,
instance_type: infra.instance_type,
}
}
async fn ensure_security_group(&self) -> Result<String> {
if let Some(ref sg_id) = self.security_group_id {
debug!("Using cached security group: {}", sg_id);
return Ok(sg_id.clone());
}
info!("Creating restrictive security group for Blueprint instances");
let sg_name = format!("blueprint-remote-{}", uuid::Uuid::new_v4());
let security_group_id = self
.provisioner
.create_security_group(&sg_name)
.await
.unwrap_or_else(|_| "default".to_string());
info!(
"Created security group: {} ({})",
sg_name, security_group_id
);
info!("Security group rules: SSH(22), QoS(8080,9615,9944), HTTPS outbound only");
Ok(security_group_id)
}
fn env_require_tee() -> bool {
std::env::var("BLUEPRINT_REMOTE_TEE_REQUIRED")
.ok()
.is_some_and(|value| {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes"
)
})
}
}
#[async_trait]
impl CloudProviderAdapter for AwsAdapter {
async fn provision_instance(
&self,
instance_type: &str,
region: &str,
require_tee: bool,
) -> Result<ProvisionedInstance> {
let spec = ResourceSpec {
cpu: 2.0,
memory_gb: 4.0,
storage_gb: 20.0,
gpu_count: None,
allow_spot: false,
qos: Default::default(),
};
let security_group = self.ensure_security_group().await?;
let mut custom_config = HashMap::new();
custom_config.insert("security_group_ids".to_string(), security_group);
custom_config.insert("instance_type".to_string(), instance_type.to_string());
custom_config.insert(
"require_tee".to_string(),
if require_tee { "true" } else { "false" }.to_string(),
);
let config = ProvisioningConfig {
name: format!("blueprint-{}", uuid::Uuid::new_v4()),
region: region.to_string(),
ssh_key_name: Some(self.key_pair_name.clone()),
ami_id: Some("ami-0c02fb55731490381".to_string()), custom_config,
..Default::default()
};
let infra = self.provisioner.provision_instance(&spec, &config).await?;
info!(
"Provisioned AWS instance {} in region {}",
infra.instance_id, region
);
Ok(Self::to_provisioned_instance(infra))
}
async fn terminate_instance(&self, instance_id: &str) -> Result<()> {
self.provisioner.terminate_instance(instance_id).await
}
async fn get_instance_status(&self, instance_id: &str) -> Result<InstanceStatus> {
self.provisioner.get_instance_status(instance_id).await
}
async fn health_check_blueprint(&self, deployment: &BlueprintDeploymentResult) -> Result<bool> {
if let Some(qos_endpoint) = deployment.qos_grpc_endpoint() {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.danger_accept_invalid_certs(false) .build()
.map_err(|e| Error::Other(format!("Failed to create secure HTTP client: {e}")))?;
match client.get(format!("{qos_endpoint}/health")).send().await {
Ok(response) => {
let is_healthy = response.status().is_success();
if is_healthy {
info!(
"Blueprint health check passed for deployment: {}",
deployment.blueprint_id
);
} else {
warn!(
"Blueprint health check failed with status: {}",
response.status()
);
}
Ok(is_healthy)
}
Err(e) => {
warn!("Blueprint health check request failed: {}", e);
Ok(false)
}
}
} else {
warn!("No QoS endpoint available for health check");
Ok(false)
}
}
async fn cleanup_blueprint(&self, deployment: &BlueprintDeploymentResult) -> Result<()> {
info!(
"Cleaning up Blueprint deployment: {}",
deployment.blueprint_id
);
self.terminate_instance(&deployment.instance.id).await
}
async fn deploy_blueprint_with_target(
&self,
target: &crate::core::deployment_target::DeploymentTarget,
blueprint_image: &str,
resource_spec: &ResourceSpec,
env_vars: HashMap<String, String>,
) -> Result<BlueprintDeploymentResult> {
use crate::core::deployment_target::DeploymentTarget;
match target {
DeploymentTarget::VirtualMachine { runtime: _ } => {
self.deploy_to_vm(blueprint_image, resource_spec, env_vars)
.await
}
DeploymentTarget::ManagedKubernetes {
cluster_id,
namespace,
} => {
self.deploy_to_eks(
cluster_id,
namespace,
blueprint_image,
resource_spec,
env_vars,
)
.await
}
DeploymentTarget::GenericKubernetes {
context: _,
namespace,
} => {
self.deploy_to_generic_k8s(namespace, blueprint_image, resource_spec, env_vars)
.await
}
DeploymentTarget::Serverless { .. } => Err(Error::Other(
"AWS Serverless deployment not implemented".into(),
)),
}
}
async fn deploy_blueprint(
&self,
instance: &ProvisionedInstance,
blueprint_image: &str,
resource_spec: &ResourceSpec,
env_vars: HashMap<String, String>,
) -> Result<BlueprintDeploymentResult> {
use crate::shared::{SharedSshDeployment, SshDeploymentConfig};
SharedSshDeployment::deploy_to_instance(
instance,
blueprint_image,
resource_spec,
env_vars,
SshDeploymentConfig::aws(),
)
.await
}
}
impl AwsAdapter {
async fn deploy_to_vm(
&self,
blueprint_image: &str,
resource_spec: &ResourceSpec,
env_vars: HashMap<String, String>,
) -> Result<BlueprintDeploymentResult> {
let require_tee = Self::env_require_tee();
let instance = self
.provision_instance("t3.medium", "us-east-1", require_tee)
.await?;
self.deploy_blueprint(&instance, blueprint_image, resource_spec, env_vars)
.await
}
async fn deploy_to_eks(
&self,
cluster_id: &str,
namespace: &str,
blueprint_image: &str,
resource_spec: &ResourceSpec,
env_vars: HashMap<String, String>,
) -> Result<BlueprintDeploymentResult> {
#[cfg(feature = "kubernetes")]
{
use crate::shared::{ManagedK8sConfig, SharedKubernetesDeployment};
let config = ManagedK8sConfig::eks("us-east-1"); SharedKubernetesDeployment::deploy_to_managed_k8s(
cluster_id,
namespace,
blueprint_image,
resource_spec,
env_vars,
config,
)
.await
}
#[cfg(not(feature = "kubernetes"))]
{
let _ = (
cluster_id,
namespace,
blueprint_image,
resource_spec,
env_vars,
); Err(Error::ConfigurationError(
"Kubernetes feature not enabled".to_string(),
))
}
}
async fn deploy_to_generic_k8s(
&self,
namespace: &str,
blueprint_image: &str,
resource_spec: &ResourceSpec,
env_vars: HashMap<String, String>,
) -> Result<BlueprintDeploymentResult> {
#[cfg(feature = "kubernetes")]
{
use crate::shared::SharedKubernetesDeployment;
SharedKubernetesDeployment::deploy_to_generic_k8s(
namespace,
blueprint_image,
resource_spec,
env_vars,
)
.await
}
#[cfg(not(feature = "kubernetes"))]
{
let _ = (namespace, blueprint_image, resource_spec, env_vars); Err(Error::ConfigurationError(
"Kubernetes feature not enabled".to_string(),
))
}
}
}