use crate::config::BlueprintManagerContext;
#[cfg(all(
feature = "blueprint-faas",
any(
feature = "aws",
feature = "gcp",
feature = "azure",
feature = "custom"
)
))]
use crate::error::Error;
use crate::error::Result;
use crate::rt::service::Service;
use crate::sources::{BlueprintArgs, BlueprintEnvVars};
use blueprint_std::path::Path;
use tracing::{info, warn};
#[derive(Debug, Clone)]
pub struct ServerlessConfig {
pub provider: FaasProviderConfig,
pub default_memory_mb: u32,
pub default_timeout_secs: u32,
pub fallback_to_vm: bool,
}
#[derive(Debug, Clone)]
pub enum FaasProviderConfig {
AwsLambda { region: String },
GcpFunctions { project_id: String },
AzureFunctions { subscription_id: String },
Custom { endpoint: String },
}
pub async fn deploy_serverless(
ctx: &BlueprintManagerContext,
service_name: &str,
binary_path: &Path,
env_vars: BlueprintEnvVars,
arguments: BlueprintArgs,
job_ids: Vec<u32>,
config: &ServerlessConfig,
) -> Result<Service> {
info!(
"Deploying service '{}' in serverless mode with {} jobs",
service_name,
job_ids.len()
);
info!("FaaS provider: {:?}", config.provider);
info!("Jobs to deploy: {:?}", job_ids);
let orchestrator_endpoint = deploy_orchestrator(
ctx,
service_name,
binary_path,
&env_vars,
&arguments,
&job_ids,
config,
)
.await?;
match &config.provider {
FaasProviderConfig::AwsLambda { .. }
| FaasProviderConfig::GcpFunctions { .. }
| FaasProviderConfig::AzureFunctions { .. } => {
for job_id in &job_ids {
deploy_job_to_faas(ctx, binary_path, *job_id, config).await?;
}
}
FaasProviderConfig::Custom { .. } => {
info!("Custom FaaS: skipping auto-deployment (deploy jobs manually)");
}
}
let runtime_dir = ctx
.runtime_dir()
.join(format!("serverless-{}", service_name));
std::fs::create_dir_all(&runtime_dir)?;
Service::new_native(
ctx,
crate::rt::ResourceLimits::default(), runtime_dir,
service_name,
binary_path,
env_vars,
arguments,
)
.await
}
async fn deploy_orchestrator(
ctx: &BlueprintManagerContext,
service_name: &str,
binary_path: &Path,
env_vars: &BlueprintEnvVars,
arguments: &BlueprintArgs,
job_ids: &[u32],
config: &ServerlessConfig,
) -> Result<String> {
info!("Deploying serverless orchestrator for '{}'", service_name);
info!("Orchestrator deployment: operator should run BlueprintRunner locally or on t4g.nano");
info!(
"Configure FaaS executors via runner config for jobs: {:?}",
job_ids
);
Ok("local-or-t4g-nano".to_string())
}
#[cfg(all(
feature = "blueprint-faas",
any(
feature = "aws",
feature = "gcp",
feature = "azure",
feature = "custom"
)
))]
async fn deploy_job_to_faas(
_ctx: &BlueprintManagerContext,
binary_path: &Path,
job_id: u32,
config: &ServerlessConfig,
) -> Result<()> {
use blueprint_faas::factory;
info!("Deploying job {} to FaaS via factory", job_id);
let binary = std::fs::read(binary_path).map_err(|e| {
Error::Other(format!(
"Failed to read binary at {}: {}",
binary_path.display(),
e
))
})?;
let provider_config = convert_to_factory_config(config)?;
let deployment = factory::deploy_job(provider_config, job_id, &binary)
.await
.map_err(|e| Error::Other(format!("FaaS deployment failed: {}", e)))?;
info!(
"Successfully deployed job {} to {}: {}",
job_id, deployment.function_id, deployment.endpoint
);
Ok(())
}
#[cfg(not(all(
feature = "blueprint-faas",
any(
feature = "aws",
feature = "gcp",
feature = "azure",
feature = "custom"
)
)))]
async fn deploy_job_to_faas(
_ctx: &BlueprintManagerContext,
_binary_path: &Path,
job_id: u32,
_config: &ServerlessConfig,
) -> Result<()> {
warn!(
"FaaS deployment requested for job {} but required features not enabled",
job_id
);
warn!("Enable blueprint-faas with at least one provider feature (aws/gcp/azure/custom)");
Ok(())
}
#[cfg(all(
feature = "blueprint-faas",
any(
feature = "aws",
feature = "gcp",
feature = "azure",
feature = "custom"
)
))]
fn convert_to_factory_config(
config: &ServerlessConfig,
) -> Result<blueprint_faas::factory::FaasProviderConfig> {
use blueprint_faas::factory::FaasProvider;
let provider = match &config.provider {
#[cfg(feature = "aws")]
FaasProviderConfig::AwsLambda { region } => {
let role_arn = std::env::var("AWS_LAMBDA_ROLE_ARN").unwrap_or_else(|_| {
warn!("AWS_LAMBDA_ROLE_ARN not set, using default role");
"arn:aws:iam::000000000000:role/blueprint-lambda-execution".to_string()
});
FaasProvider::AwsLambda {
region: region.clone(),
role_arn,
}
}
#[cfg(feature = "gcp")]
FaasProviderConfig::GcpFunctions { project_id } => {
let region = std::env::var("GCP_REGION").unwrap_or_else(|_| "us-central1".to_string());
FaasProvider::GcpFunctions {
project_id: project_id.clone(),
region,
}
}
#[cfg(feature = "azure")]
FaasProviderConfig::AzureFunctions { subscription_id } => {
let region = std::env::var("AZURE_REGION").unwrap_or_else(|_| "eastus".to_string());
FaasProvider::AzureFunctions {
subscription_id: subscription_id.clone(),
region,
}
}
#[cfg(feature = "custom")]
FaasProviderConfig::Custom { endpoint } => FaasProvider::Custom {
endpoint: endpoint.clone(),
},
#[allow(unreachable_patterns)]
_ => {
return Err(Error::Other(
"Provider not supported with current feature flags".to_string(),
));
}
};
Ok(blueprint_faas::factory::FaasProviderConfig {
provider,
default_memory_mb: config.default_memory_mb,
default_timeout_secs: config.default_timeout_secs,
})
}