Separate out to two binaries.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has failed

This commit is contained in:
Tom Alexander 2024-09-29 14:32:20 -04:00
parent cdac8224c6
commit 0602f8472b
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
7 changed files with 177 additions and 128 deletions

View File

@ -1,3 +1,5 @@
cargo-features = ["codegen-backend"]
[package] [package]
name = "webhook_bridge" name = "webhook_bridge"
version = "0.0.1" version = "0.0.1"
@ -17,6 +19,24 @@ include = [
"Cargo.lock" "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] [dependencies]
axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "http2", "json"] } axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "http2", "json"] }
base64 = "0.22.1" base64 = "0.22.1"
@ -41,3 +61,10 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
inherits = "release" inherits = "release"
lto = true lto = true
strip = "symbols" strip = "symbols"
[profile.dev]
codegen-backend = "cranelift"
[profile.dev.package."*"]
codegen-backend = "llvm"
opt-level = 3

View File

@ -6,7 +6,7 @@ RUN mkdir /source
WORKDIR /source WORKDIR /source
COPY . . COPY . .
# TODO: Add static build, which currently errors due to proc_macro. RUSTFLAGS="-C target-feature=+crt-static" # 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 FROM alpine:3.20 AS runner

9
src/app_state.rs Normal file
View File

@ -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,
}

12
src/bin_local_trigger.rs Normal file
View File

@ -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<dyn std::error::Error>> {
init_tracing().await?;
local_trigger(EXAMPLE_WEBHOOK_PAYLOAD).await
}

124
src/lib.rs Normal file
View File

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<HealthResponse>) {
(StatusCode::OK, Json(HealthResponse { ok: true }))
}
#[derive(Serialize)]
struct HealthResponse {
ok: bool,
}

View File

@ -1,133 +1,10 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use std::time::Duration; use webhookbridge::init_tracing;
use webhookbridge::launch_server;
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");
#[tokio::main] #[tokio::main]
#[allow(clippy::needless_return)] #[allow(clippy::needless_return)]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry() init_tracing().await?;
.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();
launch_server().await launch_server().await
} }
async fn launch_server() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<HealthResponse>) {
(StatusCode::OK, Json(HealthResponse { ok: true }))
}
#[derive(Serialize)]
struct HealthResponse {
ok: bool,
}

View File

@ -21,13 +21,13 @@ use serde::Serialize;
use sha2::Sha256; use sha2::Sha256;
use tracing::debug; use tracing::debug;
use crate::app_state::AppState;
use crate::discovery::discover_matching_push_triggers; use crate::discovery::discover_matching_push_triggers;
use crate::discovery::discover_webhook_bridge_config; use crate::discovery::discover_webhook_bridge_config;
use crate::gitea_client::GiteaClient; use crate::gitea_client::GiteaClient;
use crate::hook_push::HookPush; use crate::hook_push::HookPush;
use crate::hook_push::PipelineParamters; use crate::hook_push::PipelineParamters;
use crate::kubernetes::run_pipelines; use crate::kubernetes::run_pipelines;
use crate::AppState;
type HmacSha256 = Hmac<Sha256>; type HmacSha256 = Hmac<Sha256>;