use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
use crate::Error;
use crate::proto::v1::function_runner_service_server::{
FunctionRunnerService, FunctionRunnerServiceServer,
};
#[derive(clap::Parser, Debug)]
#[command(version, about = "A Crossplane composition function")]
pub struct Args {
#[arg(short, long, env = "DEBUG", default_value_t = false)]
pub debug: bool,
#[arg(long, default_value = "0.0.0.0:9443")]
pub address: String,
#[arg(long, env = "TLS_SERVER_CERTS_DIR")]
pub tls_certs_dir: Option<PathBuf>,
#[arg(long, default_value_t = false)]
pub insecure: bool,
#[arg(long)]
pub max_recv_message_size: Option<usize>,
#[arg(long, env = "METRICS_ADDRESS", default_value = ":8080")]
pub metrics_address: String,
}
pub async fn serve<F: FunctionRunnerService>(function: F, args: &Args) -> Result<(), Error> {
let address: SocketAddr = args.address.parse()?;
let mut builder = Server::builder();
if !args.insecure {
let dir = args
.tls_certs_dir
.as_deref()
.ok_or(Error::MissingTlsCertsDir)?;
builder = builder.tls_config(tls_config(dir)?)?;
}
let mut function_service = FunctionRunnerServiceServer::new(function);
if let Some(size) = args.max_recv_message_size {
function_service = function_service.max_decoding_message_size(size);
}
let reflection_v1 = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(crate::proto::FILE_DESCRIPTOR_SET)
.build_v1()?;
let reflection_v1alpha = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(crate::proto::FILE_DESCRIPTOR_SET)
.build_v1alpha()?;
let (health_reporter, health_service) = tonic_health::server::health_reporter();
health_reporter
.set_serving::<FunctionRunnerServiceServer<F>>()
.await;
if !args.metrics_address.is_empty() {
crate::metrics::initialize();
let metrics_address = args.metrics_address.clone();
tokio::spawn(async move {
if let Err(e) = crate::metrics::serve(&metrics_address).await {
tracing::error!(error = %e, "cannot serve metrics");
}
});
}
tracing::info!(%address, insecure = args.insecure, "serving FunctionRunnerService");
builder
.layer(crate::metrics::MetricsLayer)
.add_service(function_service)
.add_service(health_service)
.add_service(reflection_v1)
.add_service(reflection_v1alpha)
.serve_with_shutdown(address, shutdown_signal())
.await?;
Ok(())
}
fn tls_config(dir: &Path) -> Result<ServerTlsConfig, Error> {
let read = |name: &str| {
let path = dir.join(name);
std::fs::read(&path).map_err(|source| Error::ReadCertificate { path, source })
};
let cert = read("tls.crt")?;
let key = read("tls.key")?;
let ca = read("ca.crt")?;
Ok(ServerTlsConfig::new()
.identity(Identity::from_pem(cert, key))
.client_ca_root(Certificate::from_pem(ca))
.client_auth_optional(false))
}
#[cfg(unix)]
async fn shutdown_signal() {
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("cannot install SIGTERM handler");
tokio::select! {
_ = sigterm.recv() => {}
_ = tokio::signal::ctrl_c() => {}
}
tracing::info!("shutting down");
}
#[cfg(not(unix))]
async fn shutdown_signal() {
let _ = tokio::signal::ctrl_c().await;
tracing::info!("shutting down");
}