@@ -15,15 +15,11 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1515
1616use :: x509_cert:: Certificate ;
1717use axum:: { Router , routing:: get} ;
18- use futures_util:: { FutureExt as _ , TryFutureExt , select } ;
18+ use futures_util:: TryFutureExt ;
1919use k8s_openapi:: ByteString ;
2020use snafu:: { ResultExt , Snafu } ;
2121use stackable_telemetry:: AxumTraceLayer ;
22- use tokio:: {
23- signal:: unix:: { SignalKind , signal} ,
24- sync:: mpsc,
25- try_join,
26- } ;
22+ use tokio:: { sync:: mpsc, try_join} ;
2723use tower:: ServiceBuilder ;
2824use webhooks:: { Webhook , WebhookError } ;
2925use x509_cert:: der:: { EncodePem , pem:: LineEnding } ;
@@ -59,6 +55,7 @@ pub enum WebhookServerError {
5955///
6056/// ```
6157/// use stackable_webhook::{WebhookServer, WebhookServerOptions, webhooks::Webhook};
58+ /// use tokio::time::{Duration, sleep};
6259///
6360/// # async fn docs() {
6461/// let mut webhooks: Vec<Box<dyn Webhook>> = vec![];
@@ -69,8 +66,9 @@ pub enum WebhookServerError {
6966/// webhook_service_name: "my-operator".to_owned(),
7067/// };
7168/// let webhook_server = WebhookServer::new(webhooks, webhook_options).await.unwrap();
69+ /// let shutdown_signal = sleep(Duration::from_millis(100));
7270///
73- /// webhook_server.run().await.unwrap();
71+ /// webhook_server.run(shutdown_signal ).await.unwrap();
7472/// # }
7573/// ```
7674pub struct WebhookServer {
@@ -154,52 +152,16 @@ impl WebhookServer {
154152 } )
155153 }
156154
157- /// Runs the Webhook server and sets up signal handlers for shutting down .
155+ /// Runs the [`WebhookServer`] and handles underlying certificate rotations of the [`TlsServer`] .
158156 ///
159- /// This does not implement graceful shutdown of the underlying server. Additionally, the server
160- /// is never started in cases where no [`Webhook`] is registered. Callers of this function need
161- /// to ensure to choose the correct joining mechanism for their use-case to for example avoid
162- /// unexpected shutdowns of the whole Kubernetes controller.
163- pub async fn run ( self ) -> Result < ( ) > {
164- let future_server = self . run_server ( ) ;
165- let future_signal = async {
166- let mut sigint = signal ( SignalKind :: interrupt ( ) ) . expect ( "create SIGINT listener" ) ;
167- let mut sigterm = signal ( SignalKind :: terminate ( ) ) . expect ( "create SIGTERM listener" ) ;
168-
169- tracing:: debug!( "created unix signal handlers" ) ;
170-
171- select ! {
172- signal = sigint. recv( ) . fuse( ) => {
173- if signal. is_some( ) {
174- tracing:: debug!( "received SIGINT" ) ;
175- }
176- } ,
177- signal = sigterm. recv( ) . fuse( ) => {
178- if signal. is_some( ) {
179- tracing:: debug!( "received SIGTERM" ) ;
180- }
181- } ,
182- } ;
183- } ;
184-
185- // select requires Future + Unpin
186- tokio:: pin!( future_server) ;
187- tokio:: pin!( future_signal) ;
188-
189- tokio:: select! {
190- res = & mut future_server => {
191- // If the server future errors, propagate the error
192- res?;
193- }
194- _ = & mut future_signal => {
195- tracing:: info!( "shutdown signal received, stopping webhook server" ) ;
196- }
197- }
198-
199- Ok ( ( ) )
200- }
201-
202- async fn run_server ( self ) -> Result < ( ) > {
157+ /// It should be noted that the server is never started in cases where no [`Webhook`] is
158+ /// registered. Callers of this function need to ensure to choose the correct joining mechanism
159+ /// for their use-case to for example avoid unexpected shutdowns of the whole Kubernetes
160+ /// controller.
161+ pub async fn run < F > ( self , shutdown_signal : F ) -> Result < ( ) >
162+ where
163+ F : Future < Output = ( ) > ,
164+ {
203165 tracing:: debug!( "run webhook server" ) ;
204166
205167 let Self {
@@ -217,10 +179,14 @@ impl WebhookServer {
217179 }
218180
219181 let tls_server = tls_server
220- . run ( )
182+ . run ( shutdown_signal )
221183 . map_err ( |err| WebhookServerError :: RunTlsServer { source : err } ) ;
222184
223185 let cert_update_loop = async {
186+ // Once the shutdown signal is triggered, the TlsServer above should be dropped as the
187+ // run associated function consumes self. This in turn means that when the receiver is
188+ // polled, it will return `Ok(Ready(None))`, which will cause this while loop to break
189+ // and the future to complete.
224190 while let Some ( cert) = cert_rx. recv ( ) . await {
225191 // The caBundle needs to be provided as a base64-encoded PEM envelope.
226192 let ca_bundle = cert
@@ -243,6 +209,8 @@ impl WebhookServer {
243209 Ok ( ( ) )
244210 } ;
245211
212+ // This either returns if one of the two futures complete with Err(_) or when both complete
213+ // with Ok(_). Both futures complete with Ok(_) when a shutdown signal is received.
246214 try_join ! ( cert_update_loop, tls_server) . map ( |_| ( ) )
247215 }
248216}
0 commit comments