From 0602f8472b32471b13fe9f652011dc7176a5fa68 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 29 Sep 2024 14:32:20 -0400 Subject: [PATCH] Separate out to two binaries. --- Cargo.toml | 27 +++++++ docker/webhook_bridge/Dockerfile | 2 +- src/app_state.rs | 9 +++ src/bin_local_trigger.rs | 12 +++ src/lib.rs | 124 +++++++++++++++++++++++++++++ src/main.rs | 129 +------------------------------ src/webhook.rs | 2 +- 7 files changed, 177 insertions(+), 128 deletions(-) create mode 100644 src/app_state.rs create mode 100644 src/bin_local_trigger.rs create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 1afec1d..31fc4a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["codegen-backend"] + [package] name = "webhook_bridge" version = "0.0.1" @@ -17,6 +19,24 @@ include = [ "Cargo.lock" ] +[lib] +name = "webhookbridge" +path = "src/lib.rs" + +[[bin]] + name = "webhook_bridge" + path = "src/main.rs" + +[[bin]] + # This bin exists for development purposes only. The real target of this crate is the webhook_bridge server binary. + name = "local_trigger" + path = "src/bin_local_trigger.rs" + required-features = ["local_trigger"] + +[features] +default = ["local_trigger"] +local_trigger = [] + [dependencies] axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "http2", "json"] } base64 = "0.22.1" @@ -41,3 +61,10 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = inherits = "release" lto = true strip = "symbols" + +[profile.dev] +codegen-backend = "cranelift" + +[profile.dev.package."*"] +codegen-backend = "llvm" +opt-level = 3 diff --git a/docker/webhook_bridge/Dockerfile b/docker/webhook_bridge/Dockerfile index 0a9e7c8..607c98a 100644 --- a/docker/webhook_bridge/Dockerfile +++ b/docker/webhook_bridge/Dockerfile @@ -6,7 +6,7 @@ RUN mkdir /source WORKDIR /source COPY . . # TODO: Add static build, which currently errors due to proc_macro. RUSTFLAGS="-C target-feature=+crt-static" -RUN CARGO_TARGET_DIR=/target cargo build --profile release-lto +RUN CARGO_TARGET_DIR=/target cargo build --profile release-lto --bin webhook_bridge FROM alpine:3.20 AS runner diff --git a/src/app_state.rs b/src/app_state.rs new file mode 100644 index 0000000..65f5eb5 --- /dev/null +++ b/src/app_state.rs @@ -0,0 +1,9 @@ +use kube::Client; + +use crate::gitea_client::GiteaClient; + +#[derive(Clone)] +pub(crate) struct AppState { + pub(crate) kubernetes_client: Client, + pub(crate) gitea: GiteaClient, +} diff --git a/src/bin_local_trigger.rs b/src/bin_local_trigger.rs new file mode 100644 index 0000000..fd58345 --- /dev/null +++ b/src/bin_local_trigger.rs @@ -0,0 +1,12 @@ +#![forbid(unsafe_code)] +use webhookbridge::init_tracing; +use webhookbridge::local_trigger; + +const EXAMPLE_WEBHOOK_PAYLOAD: &str = include_str!("../example_tag_webhook_payload.json"); + +#[tokio::main] +#[allow(clippy::needless_return)] +async fn main() -> Result<(), Box> { + init_tracing().await?; + local_trigger(EXAMPLE_WEBHOOK_PAYLOAD).await +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9694e58 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,124 @@ +#![forbid(unsafe_code)] +use std::time::Duration; + +use axum::http::StatusCode; +use axum::middleware; +use axum::routing::get; +use axum::routing::post; +use axum::Json; +use axum::Router; +use kube::Client; +use serde::Serialize; +use tokio::signal; +use tower_http::timeout::TimeoutLayer; +use tower_http::trace::TraceLayer; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +use self::app_state::AppState; +use self::gitea_client::GiteaClient; +use self::hook_push::HookPush; +use self::webhook::handle_push; +use self::webhook::hook; +use self::webhook::verify_signature; + +mod app_state; +mod crd_pipeline_run; +mod discovery; +mod gitea_client; +mod hook_push; +mod kubernetes; +mod remote_config; +mod webhook; + +pub async fn init_tracing() -> Result<(), Box> { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { + "webhook_bridge=info,tower_http=debug,axum::rejection=trace".into() + }), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + Ok(()) +} + +pub async fn launch_server() -> Result<(), Box> { + let kubernetes_client: Client = Client::try_default() + .await + .expect("Set KUBECONFIG to a valid kubernetes config."); + + let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT")?; + let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; + let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); + + let app = Router::new() + .route("/hook", post(hook)) + .layer(middleware::from_fn(verify_signature)) + .route("/health", get(health)) + .layer(( + TraceLayer::new_for_http(), + // Add a timeout layer so graceful shutdown can't wait forever. + TimeoutLayer::new(Duration::from_secs(600)), + )) + .with_state(AppState { + kubernetes_client, + gitea, + }); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?; + tracing::info!("listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await?; + Ok(()) +} + +pub async fn local_trigger(payload: &str) -> Result<(), Box> { + let kubernetes_client: Client = Client::try_default() + .await + .expect("Set KUBECONFIG to a valid kubernetes config."); + + let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT")?; + let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; + let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); + + let webhook_payload: HookPush = serde_json::from_str(payload)?; + + handle_push(gitea, kubernetes_client, webhook_payload).await?; + + Ok(()) +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} + +async fn health() -> (StatusCode, Json) { + (StatusCode::OK, Json(HealthResponse { ok: true })) +} + +#[derive(Serialize)] +struct HealthResponse { + ok: bool, +} diff --git a/src/main.rs b/src/main.rs index 4fa8664..0c13227 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,133 +1,10 @@ #![forbid(unsafe_code)] -use std::time::Duration; - -use axum::http::StatusCode; -use axum::middleware; -use axum::routing::get; -use axum::routing::post; -use axum::Json; -use axum::Router; -use kube::Client; -use serde::Serialize; -use tokio::signal; -use tower_http::timeout::TimeoutLayer; -use tower_http::trace::TraceLayer; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; - -use self::gitea_client::GiteaClient; -use self::hook_push::HookPush; -use self::webhook::handle_push; -use self::webhook::hook; -use self::webhook::verify_signature; - -mod crd_pipeline_run; -mod discovery; -mod gitea_client; -mod hook_push; -mod kubernetes; -mod remote_config; -mod webhook; - -const EXAMPLE_WEBHOOK_PAYLOAD: &str = include_str!("../example_tag_webhook_payload.json"); +use webhookbridge::init_tracing; +use webhookbridge::launch_server; #[tokio::main] #[allow(clippy::needless_return)] async fn main() -> Result<(), Box> { - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { - "webhook_bridge=info,tower_http=debug,axum::rejection=trace".into() - }), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); - + init_tracing().await?; launch_server().await } - -async fn launch_server() -> Result<(), Box> { - let kubernetes_client: Client = Client::try_default() - .await - .expect("Set KUBECONFIG to a valid kubernetes config."); - - let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT")?; - let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; - let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); - - let app = Router::new() - .route("/hook", post(hook)) - .layer(middleware::from_fn(verify_signature)) - .route("/health", get(health)) - .layer(( - TraceLayer::new_for_http(), - // Add a timeout layer so graceful shutdown can't wait forever. - TimeoutLayer::new(Duration::from_secs(600)), - )) - .with_state(AppState { - kubernetes_client, - gitea, - }); - - let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?; - tracing::info!("listening on {}", listener.local_addr().unwrap()); - axum::serve(listener, app) - .with_graceful_shutdown(shutdown_signal()) - .await?; - Ok(()) -} - -async fn local_trigger() -> Result<(), Box> { - let kubernetes_client: Client = Client::try_default() - .await - .expect("Set KUBECONFIG to a valid kubernetes config."); - - let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT")?; - let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; - let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); - - let webhook_payload: HookPush = serde_json::from_str(EXAMPLE_WEBHOOK_PAYLOAD)?; - - handle_push(gitea, kubernetes_client, webhook_payload).await?; - - Ok(()) -} - -#[derive(Clone)] -struct AppState { - kubernetes_client: Client, - gitea: GiteaClient, -} - -async fn shutdown_signal() { - let ctrl_c = async { - signal::ctrl_c() - .await - .expect("failed to install Ctrl+C handler"); - }; - - #[cfg(unix)] - let terminate = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("failed to install signal handler") - .recv() - .await; - }; - - #[cfg(not(unix))] - let terminate = std::future::pending::<()>(); - - tokio::select! { - _ = ctrl_c => {}, - _ = terminate => {}, - } -} - -async fn health() -> (StatusCode, Json) { - (StatusCode::OK, Json(HealthResponse { ok: true })) -} - -#[derive(Serialize)] -struct HealthResponse { - ok: bool, -} diff --git a/src/webhook.rs b/src/webhook.rs index fe6485d..26ea141 100644 --- a/src/webhook.rs +++ b/src/webhook.rs @@ -21,13 +21,13 @@ use serde::Serialize; use sha2::Sha256; use tracing::debug; +use crate::app_state::AppState; use crate::discovery::discover_matching_push_triggers; use crate::discovery::discover_webhook_bridge_config; use crate::gitea_client::GiteaClient; use crate::hook_push::HookPush; use crate::hook_push::PipelineParamters; use crate::kubernetes::run_pipelines; -use crate::AppState; type HmacSha256 = Hmac;